import type {Project} from './catalog';

export type PlanCurrency='TMN'|'IRR'|'USDT';
export type RiskPreference='cautious'|'balanced'|'adventurous';
export type AllocationStyle='diversified'|'duration';
export type PlanInput={amount:string;currency:PlanCurrency;usdtRate:string;months:number;risk:RiskPreference;style:AllocationStyle;projectIds:string[]};
export const riskPresets={
 cautious:{reservePercent:35,maxTokenPercent:20},
 balanced:{reservePercent:20,maxTokenPercent:30},
 adventurous:{reservePercent:10,maxTokenPercent:40},
} as const;
export type PlanAllocation<Asset=Project>={project:Asset;units:number;quantity?:string;amountRial:number;shareTenths:number};
export type InvestmentPlan<Asset=Project>={budgetRial:number;investedRial:number;cashRial:number;cashShareTenths:number;allocations:PlanAllocation<Asset>[];eligibleCount:number;excludedCount:number;error:'amount'|'rate'|'range'|'selection'|'settings'|null};
const max=BigInt(Number.MAX_SAFE_INTEGER);
function decimal(value:string,places:number):bigint|null{
 if(typeof value!=='string'||value.length>80)return null;
 const normalized=value.trim().replace(/[۰-۹]/g,d=>String('۰۱۲۳۴۵۶۷۸۹'.indexOf(d))).replace(/[٠-٩]/g,d=>String('٠١٢٣٤٥٦٧٨٩'.indexOf(d))).replace(/[,٬\s]/g,'').replace(/٫/g,'.');
 if(!/^\d+(?:\.\d+)?$/.test(normalized))return null;
 const [whole,fraction='']=normalized.split('.');
 if(fraction.length>places)return null;
 return BigInt(whole)*BigInt(10)**BigInt(places)+BigInt(fraction.padEnd(places,'0')||'0');
}
export function planBudget(amount:string,currency:PlanCurrency,rate:string):{rial:bigint|null;error:InvestmentPlan['error']}{
 const input=decimal(amount,currency==='IRR'?0:currency==='TMN'?1:6);
 if(input===null||input<=BigInt(0))return {rial:null,error:'amount'};
 let rial=input;
 if(currency==='USDT'){
  const conversion=decimal(rate,1);
  if(conversion===null||conversion<=BigInt(0))return {rial:null,error:'rate'};
  rial=input*conversion/BigInt(1_000_000);
 }else if(currency!=='TMN'&&currency!=='IRR')return {rial:null,error:'settings'};
 if(rial<=BigInt(0)||rial>max)return {rial:null,error:'range'};
 return {rial,error:null};
}
export function investableProject(p:Project){
 return p.status==='published'&&Number.isSafeInteger(p.unit_price_rial)&&p.unit_price_rial>0&&Number.isSafeInteger(p.total_units)&&Number.isSafeInteger(p.sold_units)&&p.sold_units>=0&&p.total_units>p.sold_units&&Number.isSafeInteger(p.duration_months)&&p.duration_months>0;
}
function shares(amounts:bigint[],total:bigint):number[]{
 const out=amounts.map(a=>Number(a*BigInt(1000)/total));
 const order=amounts.map((a,i)=>({i,remainder:a*BigInt(1000)%total})).sort((a,b)=>a.remainder===b.remainder?a.i-b.i:a.remainder>b.remainder?-1:1);
 const missing=1000-out.reduce((s,n)=>s+n,0);
 for(let i=0;i<missing;i++)out[order[i].i]++;
 return out;
}
// A transparent planning heuristic. Preference controls reserve/concentration,
// never an invented asset risk rating, predicted return or execution order.
export function buildInvestmentPlan(projects:Project[],input:PlanInput):InvestmentPlan{
 const empty=(error:InvestmentPlan['error']):InvestmentPlan=>({budgetRial:0,investedRial:0,cashRial:0,cashShareTenths:0,allocations:[],eligibleCount:0,excludedCount:0,error});
 const budget=planBudget(input.amount,input.currency,input.usdtRate);
 if(budget.rial===null)return empty(budget.error);
 const capital=budget.rial;
 if(!Object.hasOwn(riskPresets,input.risk)||!['diversified','duration'].includes(input.style)||!Number.isInteger(input.months)||input.months<1||input.months>120)return empty('settings');
 if(!Array.isArray(input.projectIds)||!input.projectIds.length)return {...empty('selection'),budgetRial:Number(capital),cashRial:Number(capital),cashShareTenths:1000};
 const selected=new Set(input.projectIds),seen=new Set<string>();
 const eligible=projects.filter(p=>{if(seen.has(p.id))return false;seen.add(p.id);return selected.has(p.id)&&investableProject(p)&&p.duration_months<=input.months}).sort((a,b)=>a.sort_order-b.sort_order||a.slug.localeCompare(b.slug));
 const policy=riskPresets[input.risk],investable=capital*BigInt(100-policy.reservePercent)/BigInt(100),cap=capital*BigInt(policy.maxTokenPercent)/BigInt(100);
 const categoryCounts=new Map<string,number>();for(const p of eligible)categoryCounts.set(p.category,(categoryCounts.get(p.category)||0)+1);
 const weights=eligible.map(p=>input.style==='diversified'?BigInt(Math.max(1,Math.floor(12000/(categoryCounts.get(p.category)||1)))):BigInt(100+Math.floor(100*p.duration_months/input.months)));
 const prices=eligible.map(p=>BigInt(p.unit_price_rial));
 const capacities=eligible.map((p,i)=>{const supply=BigInt(p.total_units-p.sold_units)*prices[i],bound=supply<cap?supply:cap;return bound/prices[i]*prices[i]});
 const targets=eligible.map(()=>BigInt(0));
 let remaining=investable,active=eligible.map((_,i)=>i).filter(i=>capacities[i]>BigInt(0));
 // Weighted water filling, bounded by project count, never by token quantity.
 while(active.length&&remaining>BigInt(0)){
  const totalWeight=active.reduce((s,i)=>s+weights[i],BigInt(0));
  const saturated=active.filter(i=>remaining*weights[i]/totalWeight>=capacities[i]);
  if(!saturated.length){for(const i of active)targets[i]=remaining*weights[i]/totalWeight;break}
  for(const i of saturated){targets[i]=capacities[i];remaining-=capacities[i]}
  active=active.filter(i=>!saturated.includes(i));
 }
 const units=targets.map((target,i)=>target/prices[i]);
 let used=units.reduce((s,u,i)=>s+u*prices[i],BigInt(0));
 const rounding=eligible.map((_,i)=>i).sort((a,b)=>{const ra=targets[a]-units[a]*prices[a],rb=targets[b]-units[b]*prices[b];return ra===rb?a-b:ra>rb?-1:1});
 for(const i of rounding){if(units[i]*prices[i]<capacities[i]&&used+prices[i]<=investable){units[i]++;used+=prices[i]}}
 const amounts=units.map((u,i)=>u*prices[i]),cash=capital-used,percentages=shares([...amounts,cash],capital);
 return {budgetRial:Number(capital),investedRial:Number(used),cashRial:Number(cash),cashShareTenths:percentages.at(-1)!,allocations:eligible.flatMap((project,i)=>units[i]>BigInt(0)?[{project,units:Number(units[i]),amountRial:Number(amounts[i]),shareTenths:percentages[i]}]:[]),eligibleCount:eligible.length,excludedCount:selected.size-eligible.length,error:null};
}
