(function(root){
  'use strict';
  function csvRows(text){
    const rows=[];let row=[],cell='',quoted=false,ended=false;
    for(let i=0;i<text.length;i++){
      const c=text[i];
      if(quoted){if(c==='"'){if(text[i+1]==='"'){cell+='"';i++;}else{quoted=false;ended=true;}}else cell+=c;}
      else if(c==='"'&&!cell&&!ended)quoted=true;
      else if(c===','){row.push(cell);cell='';ended=false;}
      else if(c==='\n'||c==='\r'){if(c==='\r'&&text[i+1]==='\n')i++;row.push(cell);if(row.some(v=>v!==''))rows.push(row);row=[];cell='';ended=false;}
      else {if(ended||c==='"')throw Error('Invalid CSV quoting.');cell+=c;}
    }
    if(quoted)throw Error('Unclosed CSV quote.');
    row.push(cell);if(row.some(v=>v!==''))rows.push(row);return rows;
  }
  function numeric(value,label){
    if((typeof value!=='string'&&typeof value!=='number')||String(value).trim()===''||!Number.isFinite(Number(value)))throw Error('Invalid '+label+'.');
    return Number(value);
  }
  function normalize(row,index){
    if(!row||typeof row!=='object'||Array.isArray(row))throw Error('Invalid holding '+(index+1)+'.');
    const amount=numeric(row.amount,'LTC amount'),price=numeric(row.buyPrice,'buy price');
    if(amount<=0||amount>84000000||price<0||price>1e12)throw Error('Holding '+(index+1)+' has an out-of-range amount or price.');
    if(typeof row.date!=='string'||!/^\d{4}-\d{2}-\d{2}$/.test(row.date)||!Number.isFinite(Date.parse(row.date+'T00:00:00Z'))||new Date(row.date+'T00:00:00Z').toISOString().slice(0,10)!==row.date)throw Error('Invalid purchase date in holding '+(index+1)+'.');
    if(row.notes!=null&&typeof row.notes!=='string')throw Error('Notes must be text.');
    const notes=row.notes||'';if(notes.length>2000)throw Error('Notes are too long (maximum 2,000 characters).');
    return {amount:String(amount),buyPrice:String(price),date:row.date,notes};
  }
  function parse(text){
    text=text.replace(/^\uFEFF/,'').trim();if(!text)throw Error('The backup is empty.');
    if(text.length>2000000)throw Error('Maximum backup size is 2 MB.');
    let holdings;
    if(text[0]==='{'||text[0]==='['){
      let value;try{value=JSON.parse(text);}catch(e){throw Error('Invalid JSON backup.');}
      if(!value||value.format!=='litecoin.watch.portfolio'||value.version!==1||value.currency!=='USD'||!Array.isArray(value.holdings))throw Error('Unsupported portfolio backup format or version.');
      holdings=value.holdings;
    }else{
      const rows=csvRows(text),header=rows.shift()||[];
      const required=['Date','Amount LTC','Buy Price USD','Notes'];
      if(required.some(h=>header.indexOf(h)<0)||new Set(header).size!==header.length)throw Error('Use a CSV exported by Portfolio Tracker (Date, Amount LTC, Buy Price USD, Notes).');
      holdings=rows.map(row=>{if(row.length!==header.length)throw Error('A CSV row has missing or extra columns.');return {date:row[header.indexOf('Date')],amount:row[header.indexOf('Amount LTC')],buyPrice:row[header.indexOf('Buy Price USD')],notes:row[header.indexOf('Notes')]};});
    }
    if(holdings.length>1000)throw Error('Import up to 1,000 holdings at a time.');
    return holdings.map(normalize);
  }
  const fingerprint=row=>JSON.stringify([Number(row.amount),Number(row.buyPrice),row.date,row.notes||'']);
  function merge(existing,incoming){
    const seen=new Set(existing.map(fingerprint)),added=[];let duplicates=0;
    incoming.forEach(row=>{const key=fingerprint(row);if(seen.has(key))duplicates++;else{seen.add(key);added.push(row);}});
    return {added,duplicates};
  }
  function backup(holdings){return JSON.stringify({format:'litecoin.watch.portfolio',version:1,currency:'USD',exportedAt:new Date().toISOString(),holdings:holdings.map(normalize)},null,2);}
  const api={parse,merge,backup,csvRows};
  if(typeof module==='object'&&module.exports)module.exports=api;else root.LWPortfolioBackup=api;
})(typeof window!=='undefined'?window:globalThis);
