import type {Project} from './catalog';
import {investableProject,planBudget,riskPresets,type InvestmentPlan,type PlanInput} from './smart-investment';
import {wallexAssets,wallexDecimal,wallexFresh,type WallexQuote} from './wallex';
import type {WallexPlanAsset} from './wallex-investment';

export type InvestmentAsset=Project|WallexPlanAsset;
type Candidate={asset:InvestmentAsset;numerator:bigint;denominator:bigint;precision:number;factor:bigint;capacity:bigint;weight:bigint};
const zero=BigInt(0),one=BigInt(1),ten=BigInt(10),hundred=BigInt(100),thousand=BigInt(1000);
const cost=(units:bigint,row:Pick<Candidate,'numerator'|'denominator'>)=>(units*row.numerator+row.denominator-one)/row.denominator;
function quantity(units:bigint,factor:bigint,precision:number){return precision?`${units/factor}.${(units%factor).toString().padStart(precision,'0')}`.replace(/\.?0+$/,''):units.toString()}

/** One reserve and concentration policy across project units and live market tokens. */
export function buildUnifiedInvestmentPlan(projects:Project[],quotes:WallexQuote[],input:PlanInput,now=Date.now()):InvestmentPlan<InvestmentAsset>{
 const empty=(error:InvestmentPlan['error']):InvestmentPlan<InvestmentAsset>=>({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>(),policy=riskPresets[input.risk];
 const available=capital*BigInt(100-policy.reservePercent)/hundred,cap=capital*BigInt(policy.maxTokenPercent)/hundred;
 const candidates:Candidate[]=[];
 for(const project of [...projects].sort((a,b)=>a.sort_order-b.sort_order||a.slug.localeCompare(b.slug))){
  if(seen.has(project.id))continue;seen.add(project.id);
  if(!selected.has(project.id)||!investableProject(project)||project.duration_months>input.months)continue;
  const price=BigInt(project.unit_price_rial),supply=BigInt(project.total_units-project.sold_units)*price,bound=supply<cap?supply:cap;
  candidates.push({asset:project,numerator:price,denominator:one,precision:0,factor:one,capacity:bound/price*price,weight:input.style==='duration'?BigInt(100+Math.floor(100*project.duration_months/input.months)):one});
 }
 for(const quote of quotes){
  if(seen.has(quote.symbol))continue;seen.add(quote.symbol);
  const identity=wallexAssets.find(asset=>asset.symbol===quote.symbol&&asset.base===quote.base),price=wallexDecimal(quote.price);
  if(!selected.has(quote.symbol)||!identity||!wallexFresh(quote,now)||!quote.spot||!Number.isInteger(quote.amountPrecision)||quote.amountPrecision===null||quote.amountPrecision<0||quote.amountPrecision>12||price===null)continue;
  const [whole,fraction='']=price.split('.'),precision=quote.amountPrecision,factor=ten**BigInt(precision);
  // Toman quotes may have decimals; preserve their rational value before rounding quantity down.
  const numerator=BigInt(whole+fraction)*ten,denominator=ten**BigInt(fraction.length)*factor;
  if(numerator<=zero)continue;
  const row={numerator,denominator},capacity=cost(cap*denominator/numerator,row);
  candidates.push({asset:{id:quote.symbol,symbol:quote.base,title:{fa:quote.name,en:identity.description},wallex:quote},...row,precision,factor,capacity,weight:input.style==='duration'?hundred:one});
 }
 const base={...empty(null),budgetRial:Number(capital),cashRial:Number(capital),cashShareTenths:1000,eligibleCount:candidates.length,excludedCount:selected.size-candidates.length};
 if(!candidates.length)return base;
 const targets=candidates.map(()=>zero);
 let remaining=available,active=candidates.map((_,i)=>i).filter(i=>candidates[i].capacity>zero);
 // Redistribute only known capacity limits; market volume is never treated as token supply.
 while(active.length&&remaining>zero){
  const weight=active.reduce((sum,i)=>sum+candidates[i].weight,zero);
  const saturated=active.filter(i=>remaining*candidates[i].weight/weight>=candidates[i].capacity);
  if(!saturated.length){for(const i of active)targets[i]=remaining*candidates[i].weight/weight;break}
  for(const i of saturated){targets[i]=candidates[i].capacity;remaining-=targets[i]}
  active=active.filter(i=>!saturated.includes(i));
 }
 const rows=candidates.flatMap((row,i)=>{
  const units=targets[i]*row.denominator/row.numerator;if(units===zero)return [];
  const text=quantity(units,row.factor,row.precision);
  return [{project:row.asset,units:Number(text),quantity:text,amountRial:Number(cost(units,row)),shareTenths:0}];
 });
 const spent=rows.map(row=>BigInt(row.amountRial)),used=spent.reduce((sum,value)=>sum+value,zero),cash=capital-used,amounts=[...spent,cash];
 const shares=amounts.map(value=>Number(value*thousand/capital));
 const remainders=amounts.map((value,i)=>({i,value:value*thousand%capital})).sort((a,b)=>a.value===b.value?a.i-b.i:a.value>b.value?-1:1);
 for(let i=0,left=1000-shares.reduce((sum,value)=>sum+value,0);i<left;i++)shares[remainders[i].i]++;
 return {...base,investedRial:Number(used),cashRial:Number(cash),cashShareTenths:shares.at(-1)!,allocations:rows.map((row,i)=>({...row,shareTenths:shares[i]}))};
}
