/* eslint-disable no-undef */
const { useState, useEffect, useRef, useCallback } = React;

// ---------- Constants ----------
const CC_COLUMNS = ["Date", "Payee", "Memo", "Outflow", "Inflow"];

const FIELD_META = {
  Date:    { hint: "Day of the transaction",          tone: "neutral", required: true  },
  Payee:   { hint: "Who you paid or who paid you",    tone: "neutral", required: false },
  Memo:    { hint: "What the transaction was for",    tone: "neutral", required: false },
  Outflow: { hint: "Money leaving the account",       tone: "out",     required: true  },
  Inflow:  { hint: "Money coming in",                 tone: "in",      required: true  },
};

// Sample data (Scotiabank demo)
const SAMPLE_FILE = "scotia-checking-march.csv";
const SAMPLE_HEADERS = [
  "2026-03-09 (1)",
  "AMEX CARDS L4U8J8 (1)",
  "6907.81 (1)",
  "Unnamed column (1)",
  "3300.37 (1)",
];
const SAMPLE_ROWS = [
  ["2026-03-09", "RI102 TFR-FR 7306381",      "",        "9254.7",  "12555.07"],
  ["2026-03-09", "G-Transfer **775",           "4104.16", "",        "8450.91"],
  ["2026-03-09", "SEND E-TFR ***Jc9",          "113",     "",        "8337.91"],
  ["2026-03-10", "CPL: INS",                   "",        "79.2",    "8417.11"],
  ["2026-03-10", "CANADA PRO",                 "",        "53.51",   "8470.62"],
  ["2026-03-10", "260310S7053200WIRE",         "",        "8568.26", "17038.88"],
  ["2026-03-13", "JG455 TFR-TO C/C",           "10000",   "",        "7038.88"],
  ["2026-03-13", "CONSUMER LOANS LOAN",        "593.47",  "",        "6445.41"],
  ["2026-03-16", "SEND E-TFR ***SRJ",          "2000",    "",        "4445.41"],
  ["2026-03-16", "UO301 TFR-FR 7306381",       "",        "20148",   "24593.41"],
];
const SAMPLE_MAPPING = { "0": "Date", "1": "Memo", "2": "Outflow", "3": "Inflow", "4": "" };

// ---------- CSV helpers ----------
function autoDetectMapping(headers) {
  const lower = headers.map(h => h.toLowerCase());
  const KEYWORDS = {
    Date:    ["date", "datum", "posted", "trans date", "transaction date", "booking date", "value date"],
    Payee:   ["payee", "merchant", "vendor", "beneficiary", "counterpart"],
    Memo:    ["memo", "description", "narrative", "notes", "details", "reference", "remarks"],
    Outflow: ["debit", "outflow", "withdrawal", "dr", "charge", "payment out", "money out"],
    Inflow:  ["credit", "inflow", "deposit", "cr", "payment in", "money in"],
  };

  const mapping = {};
  headers.forEach((_, i) => { mapping[String(i)] = ""; });

  const assigned = new Set();
  CC_COLUMNS.forEach(col => {
    for (let i = 0; i < lower.length; i++) {
      if (assigned.has(i)) continue;
      if (KEYWORDS[col].some(kw => lower[i].includes(kw))) {
        mapping[String(i)] = col;
        assigned.add(i);
        break;
      }
    }
  });

  return mapping;
}

function deduplicateHeaders(raw) {
  const seen = {};
  return raw.map(h => {
    const base = h.trim() || "Unnamed column";
    if (seen[base] === undefined) { seen[base] = 0; return base; }
    seen[base]++;
    return `${base} (${seen[base]})`;
  });
}

function parseCSVText(text, cfg) {
  const { delimiter, hasHeader, startAtRow } = cfg;

  const config = {
    header: false,
    skipEmptyLines: true,
    beforeFirstChunk(chunk) {
      const rows = chunk.split("\n");
      return rows.slice((startAtRow || 1) - 1).join("\n");
    },
  };

  if (delimiter !== "auto") config.delimiter = delimiter;

  const result = Papa.parse(text, config);
  const allRows = result.data;
  if (!allRows.length) return { headers: [], rows: [] };

  if (hasHeader) {
    const headers = deduplicateHeaders(allRows[0]);
    const rows = allRows.slice(1).map(r => headers.map((_, i) => r[i] ?? ""));
    return { headers, rows };
  } else {
    const maxCols = Math.max(...allRows.map(r => r.length));
    const headers = Array.from({ length: maxCols }, (_, i) => `Column ${i + 1}`);
    return { headers, rows: allRows.map(r => headers.map((_, i) => r[i] ?? "")) };
  }
}

function getConvertedRow(row, mapping, invertedOutflow) {
  const outflowEntry = Object.entries(mapping).find(([, v]) => v === "Outflow");
  const inflowEntry  = Object.entries(mapping).find(([, v]) => v === "Inflow");
  const outflowIdx = outflowEntry ? outflowEntry[0] : null;
  const inflowIdx  = inflowEntry  ? inflowEntry[0]  : null;
  const sameCol = outflowIdx !== null && inflowIdx !== null && outflowIdx === inflowIdx;

  return CC_COLUMNS.map(col => {
    const entry = Object.entries(mapping).find(([, v]) => v === col);
    if (!entry) return "";
    const cell = row[Number(entry[0])] || "";

    if (col === "Outflow") {
      if (!cell) return "";
      if (sameCol) return !invertedOutflow ? (cell.startsWith("-") ? cell.slice(1) : "") : (cell.startsWith("-") ? "" : cell);
      return cell.startsWith("-") ? cell.slice(1) : cell;
    }
    if (col === "Inflow") {
      if (!cell) return "";
      if (sameCol) return !invertedOutflow ? (cell.startsWith("-") ? "" : cell) : (cell.startsWith("-") ? cell.slice(1) : "");
      return cell.startsWith("-") ? cell.slice(1) : cell;
    }
    return cell;
  });
}

function buildConvertedCSV(headers, rows, mapping, invertedOutflow) {
  let csv = '"' + CC_COLUMNS.join('","') + '"\n';
  rows.forEach(row => {
    const vals = getConvertedRow(row, mapping, invertedOutflow);
    csv += '"' + vals.map(v => v.replace(/"/g, '""').trim()).join('","') + '"\n';
  });
  return csv;
}

function computeDateRange(rows, mapping) {
  const entry = Object.entries(mapping).find(([, v]) => v === "Date");
  if (!entry) return "";
  const idx = Number(entry[0]);
  const dates = rows.map(r => new Date(r[idx])).filter(d => !isNaN(d));
  if (!dates.length) return "";
  const min = new Date(Math.min(...dates.map(d => d.getTime())));
  const max = new Date(Math.max(...dates.map(d => d.getTime())));
  const fmt = d => d.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
  return min.getTime() === max.getTime() ? fmt(min) : `${fmt(min)} – ${fmt(max)}`;
}

// ---------- Inline icons ----------
const Icon = {
  upload: p => (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}>
      <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
      <polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/>
    </svg>
  ),
  file: p => (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}>
      <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
      <polyline points="14 2 14 8 20 8"/>
    </svg>
  ),
  download: p => (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}>
      <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
      <polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/>
    </svg>
  ),
  refresh: p => (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}>
      <polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/>
      <path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10"/>
      <path d="M20.49 15a9 9 0 0 1-14.85 3.36L1 14"/>
    </svg>
  ),
  swap: p => (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}>
      <polyline points="17 1 21 5 17 9"/><path d="M3 11V9a4 4 0 0 1 4-4h14"/>
      <polyline points="7 23 3 19 7 15"/><path d="M21 13v2a4 4 0 0 1-4 4H3"/>
    </svg>
  ),
  check: p => (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" {...p}>
      <polyline points="20 6 9 17 4 12"/>
    </svg>
  ),
  sun: p => (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}>
      <circle cx="12" cy="12" r="4"/>
      <path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"/>
    </svg>
  ),
  moon: p => (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}>
      <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
    </svg>
  ),
  chevron: p => (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}>
      <polyline points="6 9 12 15 18 9"/>
    </svg>
  ),
  x: p => (
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" {...p}>
      <line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
    </svg>
  ),
};

// ---------- Header ----------
function Header({ theme, onToggleTheme }) {
  return (
    <header className="cc-header">
      <div className="cc-header__inner">
        <div className="cc-brand">
          <svg width="189" height="32" viewBox="0 0 1135 192" fill="none" xmlns="http://www.w3.org/2000/svg" aria-label="Cash Coach">
            <path fillRule="evenodd" clipRule="evenodd" d="M89.4163 104.731L129.726 65.2538C161.906 33.7392 214.079 33.7392 246.258 65.2537L267.879 86.4277L297.928 56.9995L276.307 35.8255C227.532 -11.9419 148.452 -11.9418 99.677 35.8255L59.3672 75.3024L89.4163 104.731ZM208.512 86.4284L168.202 125.905C136.023 157.42 83.8493 157.42 51.6699 125.905L30.0491 104.731L0 134.16L21.6207 155.334C70.3958 203.101 149.476 203.101 198.251 155.334L238.561 115.857L208.512 86.4284Z" fill="#E0FF70"/>
            <path fillRule="evenodd" clipRule="evenodd" d="M673.394 147.744V108.955C673.394 107.079 673.29 104.696 673.083 101.806C672.876 98.8651 672.229 95.9243 671.142 92.9835C670.054 90.0426 668.268 87.5835 665.783 85.6061C663.35 83.6286 659.881 82.6399 655.376 82.6399C653.564 82.6399 651.623 82.9188 649.552 83.4765C647.481 84.0343 645.54 85.1244 643.727 86.7469C641.915 88.3187 640.414 90.6511 639.223 93.744C638.084 96.837 637.515 100.944 637.515 106.065V147.744H616.08V38.2234H634.874V74.0496C636.805 71.9332 639.032 70.0558 641.553 68.4175C646.989 64.9189 653.823 63.1696 662.055 63.1696C668.631 63.1696 673.989 64.2598 678.131 66.44C682.273 68.6203 685.483 71.3837 687.761 74.7301C690.091 78.0766 691.747 81.5751 692.731 85.2258C693.715 88.8258 694.31 92.1215 694.517 95.113C694.724 98.1045 694.828 100.285 694.828 101.654V147.744H673.394ZM1123.51 147.744V105.837C1123.51 101.274 1123.07 97.0905 1122.19 93.2877C1121.31 89.4342 1119.88 86.0878 1117.91 83.2483C1116 80.3582 1113.49 78.1273 1110.38 76.5554C1107.33 74.9836 1103.6 74.1977 1099.2 74.1977C1095.16 74.1977 1091.59 74.8822 1088.48 76.2512C1085.43 77.6202 1082.84 79.5977 1080.71 82.1836C1078.64 84.7188 1077.06 87.8117 1075.98 91.4624C1074.89 95.113 1074.35 99.2707 1074.35 103.935V147.744H1062.85V38.2234H1073.18V77.0133C1075.87 73.2002 1079.21 70.1828 1083.2 67.9612C1088.48 65.0203 1094.54 63.5499 1101.37 63.5499C1106.39 63.5499 1110.77 64.3105 1114.5 65.8316C1118.28 67.3527 1121.46 69.4062 1124.05 71.9921C1126.69 74.578 1128.81 77.5442 1130.42 80.8906C1132.02 84.1864 1133.19 87.7103 1133.91 91.4624C1134.64 95.1637 1135 98.8651 1135 102.566V147.744H1123.51ZM1011.72 150.024C1003.28 150.024 996.137 148.174 990.286 144.472C984.436 140.771 979.983 135.675 976.929 129.185C973.926 122.644 972.373 115.14 972.269 106.673C972.373 98.0531 973.978 90.4983 977.084 84.0082C980.191 77.4674 984.669 72.397 990.519 68.7971C996.37 65.1464 1003.46 63.3211 1011.8 63.3211C1020.29 63.3211 1027.67 65.3492 1033.93 69.4055C1040.25 73.4618 1044.57 79.0139 1046.9 86.0617L1035.72 89.5603C1033.75 84.642 1030.62 80.8392 1026.32 78.1519C1022.08 75.4139 1017.21 74.0449 1011.72 74.0449C1005.56 74.0449 1000.46 75.4393 996.422 78.228C992.383 80.966 989.38 84.7941 987.413 89.7124C985.446 94.6306 984.436 100.284 984.384 106.673C984.488 116.509 986.818 124.419 991.374 130.402C995.93 136.334 1002.71 139.301 1011.72 139.301C1017.42 139.301 1022.26 138.033 1026.24 135.498C1030.28 132.912 1033.34 129.16 1035.41 124.242L1046.9 127.588C1043.79 134.889 1039.24 140.467 1033.23 144.32C1027.23 148.123 1020.06 150.024 1011.72 150.024ZM898.7 146.678C902.997 148.909 908.304 150.024 914.62 150.024C923.008 150.024 930.204 148.174 936.21 144.472C940.434 141.869 943.877 138.438 946.539 134.179V147.743H956.712V96.7095C956.712 93.6673 956.557 90.6758 956.246 87.7349C955.935 84.7941 955.262 82.1322 954.227 79.7491C952.001 74.4252 948.195 70.3689 942.811 67.5802C937.478 64.7408 930.981 63.3211 923.318 63.3211C914.051 63.3211 906.518 65.3492 900.719 69.4055C894.92 73.4618 891.063 79.166 889.148 86.518L900.253 89.7124C901.858 84.2364 904.628 80.2308 908.563 77.6956C912.549 75.1604 917.416 73.8928 923.163 73.8928C931.498 73.8928 937.349 75.8449 940.714 79.7491C943.818 83.3498 945.424 88.7402 945.533 95.9204C942.799 96.2992 939.794 96.689 936.52 97.0898C931.602 97.6475 926.554 98.332 921.377 99.1433C916.251 99.9038 911.566 100.791 907.32 101.805C903.127 102.87 899.425 104.416 896.215 106.445C893.057 108.473 890.546 111.084 888.682 114.278C886.87 117.422 885.964 121.225 885.964 125.687C885.964 130.047 887.025 134.078 889.148 137.78C891.27 141.481 894.454 144.447 898.7 146.678ZM929.919 137.551C925.933 139.427 921.273 140.365 915.94 140.365C911.436 140.365 907.812 139.63 905.068 138.16C902.376 136.689 900.408 134.839 899.166 132.608C897.975 130.377 897.38 128.07 897.38 125.687C897.38 122.746 898.079 120.287 899.476 118.309C900.926 116.332 902.764 114.76 904.99 113.594C907.217 112.377 909.521 111.439 911.902 110.78C915.423 109.867 919.358 109.081 923.706 108.422C928.107 107.763 932.56 107.18 937.064 106.673C939.965 106.35 942.729 106.027 945.356 105.704C945.344 106.941 945.323 108.404 945.296 110.095C945.244 113.239 944.908 116.129 944.286 118.766C943.562 122.873 941.983 126.574 939.549 129.87C937.116 133.115 933.906 135.675 929.919 137.551ZM833.728 150.024C825.393 150.024 818.274 148.173 812.372 144.472C806.469 140.771 801.939 135.65 798.781 129.109C795.623 122.568 794.044 115.064 794.044 106.596C794.044 97.9766 795.649 90.4218 798.859 83.9317C802.069 77.4416 806.625 72.3966 812.527 68.7966C818.481 65.1459 825.548 63.3206 833.728 63.3206C842.115 63.3206 849.26 65.1713 855.162 68.8727C861.116 72.5233 865.647 77.6191 868.753 84.1599C871.911 90.6499 873.49 98.1287 873.49 106.596C873.49 115.216 871.911 122.796 868.753 129.337C865.595 135.827 861.039 140.897 855.085 144.548C849.131 148.199 842.012 150.024 833.728 150.024ZM833.728 139.3C843.047 139.3 849.985 136.283 854.541 130.25C859.097 124.165 861.375 116.281 861.375 106.596C861.375 96.6583 859.071 88.7485 854.463 82.8669C849.907 76.9853 842.996 74.0444 833.728 74.0444C827.464 74.0444 822.286 75.4388 818.196 78.2275C814.158 80.9655 811.129 84.7937 809.11 89.7119C807.142 94.5795 806.159 100.208 806.159 106.596C806.159 116.483 808.489 124.419 813.148 130.402C817.808 136.334 824.668 139.3 833.728 139.3ZM728.336 144.472C734.186 148.174 741.331 150.024 749.77 150.024C758.106 150.024 765.276 148.123 771.282 144.32C777.288 140.467 781.844 134.889 784.95 127.588L773.457 124.242C771.386 129.16 768.331 132.912 764.293 135.498C760.306 138.033 755.465 139.301 749.77 139.301C740.762 139.301 733.979 136.334 729.423 130.402C724.867 124.419 722.537 116.509 722.434 106.673C722.486 100.284 723.495 94.6306 725.462 89.7124C727.43 84.7941 730.433 80.966 734.471 78.228C738.509 75.4393 743.609 74.0449 749.77 74.0449C755.258 74.0449 760.125 75.4139 764.37 78.1519C768.667 80.8392 771.8 84.642 773.767 89.5603L784.95 86.0617C782.62 79.0139 778.297 73.4618 771.981 69.4055C765.716 65.3492 758.339 63.3211 749.848 63.3211C741.512 63.3211 734.419 65.1464 728.569 68.7971C722.719 72.397 718.24 77.4674 715.134 84.0082C712.027 90.4983 710.422 98.0531 710.319 106.673C710.422 115.14 711.975 122.644 714.978 129.185C718.033 135.675 722.486 140.771 728.336 144.472ZM538.435 143.027C544.958 147.692 553.501 150.024 564.063 150.024C575.039 150.024 583.633 147.616 589.846 142.799C596.059 137.982 599.165 131.365 599.165 122.948C599.165 118.689 598.285 115.064 596.525 112.072C594.764 109.03 591.943 106.444 588.06 104.315C584.228 102.134 579.129 100.233 572.761 98.6104C566.237 96.9879 561.37 95.6696 558.16 94.6555C555.002 93.6415 552.905 92.6527 551.87 91.6894C550.886 90.726 550.394 89.5345 550.394 88.1147C550.394 85.7317 551.559 83.9063 553.889 82.6387C556.271 81.3711 559.403 80.8641 563.286 81.1176C567.273 81.4218 570.431 82.4613 572.761 84.2359C575.09 86.0105 576.437 88.419 576.799 91.4612L598.388 87.6584C597.664 82.6894 595.722 78.3796 592.564 74.729C589.458 71.0783 585.367 68.2642 580.294 66.2868C575.22 64.3093 569.395 63.3206 562.82 63.3206C556.038 63.3206 550.136 64.3854 545.114 66.5149C540.143 68.5938 536.286 71.5853 533.542 75.4895C530.798 79.343 529.426 83.9063 529.426 89.1795C529.426 93.3879 530.332 96.9879 532.144 99.9794C533.956 102.92 536.907 105.455 540.998 107.585C545.088 109.715 550.55 111.641 557.384 113.365C563.441 114.937 567.894 116.205 570.741 117.168C573.641 118.131 575.505 119.12 576.333 120.134C577.161 121.098 577.576 122.39 577.576 124.013C577.576 126.599 576.54 128.627 574.469 130.097C572.398 131.517 569.447 132.227 565.616 132.227C560.956 132.227 557.099 131.137 554.044 128.957C551.042 126.776 549.1 123.759 548.22 119.906L526.63 123.1C528.028 131.669 531.963 138.311 538.435 143.027ZM464.036 150.024C458.03 150.024 452.931 148.909 448.737 146.678C444.595 144.396 441.437 141.379 439.263 137.627C437.14 133.824 436.079 129.641 436.079 125.078C436.079 121.275 436.674 117.802 437.865 114.658C439.055 111.515 440.971 108.751 443.612 106.368C446.304 103.934 449.902 101.906 454.406 100.284C457.513 99.1682 461.215 98.1794 465.512 97.3175C469.809 96.4555 474.676 95.6443 480.112 94.8837C483.312 94.4062 486.7 93.9024 490.276 93.3722C489.874 89.9858 488.739 87.3967 486.868 85.6049C484.487 83.3232 480.5 82.1824 474.909 82.1824C471.802 82.1824 468.566 82.9176 465.201 84.388C461.836 85.8584 459.48 88.4697 458.134 92.2218L439.03 86.2894C441.152 79.4951 445.139 73.9684 450.989 69.7093C456.84 65.4502 464.813 63.3206 474.909 63.3206C482.312 63.3206 488.887 64.4361 494.634 66.6671C500.381 68.898 504.73 72.7515 507.681 78.2275C509.338 81.2697 510.322 84.312 510.632 87.3542C510.943 90.3964 511.098 93.7936 511.098 97.5456V147.742H492.615V137.659C489.278 141.51 485.731 144.414 481.976 146.373C477.264 148.807 471.284 150.024 464.036 150.024ZM468.541 133.748C472.424 133.748 475.685 133.089 478.326 131.771C481.018 130.402 483.141 128.855 484.694 127.131C486.299 125.407 487.386 123.962 487.956 122.796C489.043 120.565 489.664 117.979 489.819 115.038C489.946 113.216 490.033 111.581 490.083 110.135C486.604 110.717 483.617 111.236 481.121 111.692C477.238 112.351 474.106 112.96 471.725 113.517C469.343 114.075 467.246 114.684 465.434 115.343C463.363 116.154 461.681 117.041 460.386 118.005C459.144 118.917 458.212 119.931 457.59 121.047C457.021 122.162 456.736 123.405 456.736 124.774C456.736 126.65 457.202 128.272 458.134 129.641C459.118 130.959 460.49 131.973 462.25 132.683C464.01 133.393 466.107 133.748 468.541 133.748ZM366.304 144.32C372.517 148.123 379.972 150.024 388.67 150.024C398.403 150.024 406.584 147.666 413.211 142.951C419.838 138.235 424.342 131.517 426.724 122.796L405.134 118.385C403.891 122.34 402.028 125.382 399.542 127.512C397.057 129.641 393.433 130.706 388.67 130.706C382.302 130.706 377.487 128.5 374.225 124.089C371.015 119.627 369.41 113.822 369.41 106.672C369.41 102.109 370.083 98.0273 371.429 94.4274C372.776 90.7767 374.872 87.9119 377.72 85.8331C380.567 83.7035 384.217 82.6387 388.67 82.6387C392.553 82.6387 396.022 83.8303 399.076 86.2134C402.183 88.5964 404.357 91.9175 405.6 96.1766L426.724 90.7006C424.86 82.2331 420.562 75.5656 413.832 70.698C407.153 65.7797 398.869 63.3206 388.981 63.3206C380.438 63.3206 373.034 65.1966 366.77 68.9487C360.505 72.7008 355.639 77.8472 352.17 84.388C348.753 90.9288 347.044 98.3569 347.044 106.672C347.044 114.886 348.701 122.264 352.014 128.805C355.328 135.345 360.091 140.517 366.304 144.32Z" fill="currentColor"/>
          </svg>
        </div>

        <div className="cc-header__actions">
          <button className="cc-icon-btn" onClick={onToggleTheme}
                  title={theme === "dark" ? "Switch to light" : "Switch to dark"}>
            {theme === "dark" ? <Icon.sun width="16" height="16"/> : <Icon.moon width="16" height="16"/>}
          </button>
        </div>
      </div>
    </header>
  );
}

// ---------- Shared header toggle ----------
function HeaderToggle({ hasHeader, onToggle, onClick }) {
  return (
    <label className="cc-header-toggle" onClick={onClick}>
      <div className={`cc-toggle ${hasHeader ? "is-on" : ""}`} onClick={onToggle}>
        <div className="cc-toggle__thumb"/>
      </div>
      <span>My file has column names in the first row</span>
    </label>
  );
}

// ---------- Drop zone ----------
function DropZone({ hasHeader, onToggleHeader, onRawLoaded, onSample }) {
  const [hover, setHover] = useState(false);
  const [cfgOpen, setCfgOpen] = useState(false);
  const [cfg, setCfg] = useState({ delimiter: "auto", encoding: "UTF-8" });
  const inputRef = useRef(null);

  const readFile = useCallback((file) => {
    const reader = new FileReader();
    reader.onload = e => {
      onRawLoaded(e.target.result, file.name, cfg.delimiter, cfg.encoding);
    };
    reader.readAsText(file, cfg.encoding);
  }, [cfg, onRawLoaded]);

  const handleInputChange = e => {
    const file = e.target.files[0];
    if (file) readFile(file);
    e.target.value = "";
  };

  const handleDrop = e => {
    e.preventDefault();
    setHover(false);
    const file = e.dataTransfer.files[0];
    if (file) readFile(file);
  };

  return (
    <section className="cc-drop">
      <div className="cc-drop__ornament" aria-hidden="true"/>

      <div
        className={`cc-drop__zone ${hover ? "is-hover" : ""}`}
        onDragOver={e => { e.preventDefault(); setHover(true); }}
        onDragLeave={() => setHover(false)}
        onDrop={handleDrop}
        onClick={() => inputRef.current?.click()}
      >
        <div className="cc-drop__icon"><Icon.upload width="28" height="28"/></div>
        <h1 className="cc-drop__title">Drop your bank CSV here</h1>
        <p className="cc-drop__sub">
          We'll map the columns to Cash Coach format and clean it up for import.
          Your file never leaves your browser.
        </p>

        <div className="cc-drop__actions" onClick={e => e.stopPropagation()}>
          <label className="cc-btn cc-btn--primary" style={{ cursor: "pointer" }}>
            <input
              ref={inputRef}
              type="file"
              id="file"
              accept=".csv,.txt"
              style={{ display: "none" }}
              onChange={handleInputChange}
            />
            <Icon.file width="14" height="14"/>
            <span>Choose file</span>
          </label>

          <div className={`cc-cfg ${cfgOpen ? "is-open" : ""}`}>
            <button className="cc-cfg__trigger" onClick={() => setCfgOpen(o => !o)}>
              <span>More options</span>
              <Icon.chevron width="11" height="11"/>
            </button>
            {cfgOpen && (
              <div className="cc-cfg__panel">
                <label className="cc-cfg__row">
                  <span>Delimiter</span>
                  <select value={cfg.delimiter} onChange={e => setCfg({ ...cfg, delimiter: e.target.value })}>
                    <option value="auto">Auto-detect</option>
                    <option value=",">Comma ,</option>
                    <option value=";">Semicolon ;</option>
                    <option value={"\t"}>Tab</option>
                    <option value="|">Pipe |</option>
                  </select>
                </label>
                <label className="cc-cfg__row">
                  <span>Encoding</span>
                  <select value={cfg.encoding} onChange={e => setCfg({ ...cfg, encoding: e.target.value })}>
                    <option>UTF-8</option>
                    <option>ISO-8859-1</option>
                    <option>Windows-1252</option>
                  </select>
                </label>
              </div>
            )}
          </div>
        </div>

        <HeaderToggle
          hasHeader={hasHeader}
          onToggle={onToggleHeader}
          onClick={e => e.stopPropagation()}
        />

        <div className="cc-drop__hint">
          <span>Or try a</span>
          <button className="cc-link" onClick={e => { e.stopPropagation(); onSample(); }}>
            sample file from Scotiabank
          </button>
        </div>
      </div>
    </section>
  );
}

// ---------- Mapping sidebar ----------
function MappingSidebar({ headers, mapping, onChangeMapping, filename, totalRows, dateRange, mappedCount, onSave, onLoadDifferent, hasHeader, onToggleHeader }) {
  return (
    <aside className="cc-side">
      <div className="cc-side__file">
        <div className="cc-side__file-icon"><Icon.file width="16" height="16"/></div>
        <div className="cc-side__file-text">
          <div className="cc-side__file-name" title={filename}>{filename}</div>
          <div className="cc-side__file-meta">{totalRows} rows{dateRange ? ` · ${dateRange}` : ""}</div>
        </div>
      </div>

      <HeaderToggle hasHeader={hasHeader} onToggle={onToggleHeader}/>

      <div className="cc-side__heading">
        <h2 className="cc-side__title">Match your columns</h2>
        <p className="cc-side__desc">
          We matched <strong>{mappedCount} of 5</strong> automatically. Fix anything that looks wrong.
        </p>
      </div>

      <ul className="cc-fields">
        {CC_COLUMNS.map(col => {
          const meta = FIELD_META[col];
          const sourceIdx = Object.entries(mapping).find(([, v]) => v === col)?.[0] ?? "";
          const mapped = sourceIdx !== "";
          return (
            <li key={col} className={`cc-field cc-field--${meta.tone} ${mapped ? "is-mapped" : "is-unmapped"}`}>
              <div className="cc-field__row">
                <div className="cc-field__label">
                  <span className="cc-field__name">{col}</span>
                  {meta.required && !mapped && <span className="cc-field__req">required</span>}
                  {mapped && <span className="cc-field__ok"><Icon.check width="10" height="10"/></span>}
                </div>
                <span className="cc-field__hint">{meta.hint}</span>
              </div>
              <div className="cc-field__select">
                <select value={sourceIdx} onChange={e => onChangeMapping(col, e.target.value)}>
                  <option value="">— not used —</option>
                  {headers.map((h, i) => (
                    <option key={i} value={i}>{h}</option>
                  ))}
                </select>
                <Icon.chevron width="12" height="12" className="cc-field__caret"/>
              </div>
            </li>
          );
        })}
      </ul>

      <button className="cc-cta" onClick={onSave}>
        <span className="cc-cta__icon"><Icon.download width="14" height="14"/></span>
        <span className="cc-cta__text">
          <span className="cc-cta__line1">Save Cash Coach CSV</span>
          <span className="cc-cta__line2">{totalRows} transactions ready</span>
        </span>
      </button>

      <button className="cc-btn-sm cc-btn-sm--ghost" style={{ width: "100%", justifyContent: "center" }} onClick={onLoadDifferent}>
        <Icon.refresh width="14" height="14"/>
        <span>Try a different file</span>
      </button>

      <div className="cc-privacy">
        <span className="cc-dot"/> Processed locally — your file never leaves this browser.
      </div>
    </aside>
  );
}

// ---------- Preview pane ----------
function PreviewPane({ headers, rows, mapping, invertedOutflow, onFlip }) {
  const [activeTab, setActiveTab] = useState("output");

  return (
    <section className="cc-preview">
      <div className="cc-preview__tabs">
        <div
          className={`cc-preview__tab ${activeTab === "output" ? "is-active" : ""}`}
          onClick={() => setActiveTab("output")}
        >
          <span className={`cc-preview__tab-dot ${activeTab !== "output" ? "cc-preview__tab-dot--quiet" : ""}`}/>
          Ready to import
          <span className="cc-preview__tab-count">{rows.length}</span>
        </div>
        <div
          className={`cc-preview__tab ${activeTab === "original" ? "is-active" : ""}`}
          onClick={() => setActiveTab("original")}
        >
          <span className={`cc-preview__tab-dot ${activeTab !== "original" ? "cc-preview__tab-dot--quiet" : ""}`}/>
          Your original file
        </div>
        <div className="cc-preview__spacer"/>
        <button className="cc-pillbtn cc-pillbtn--ghost" onClick={onFlip}>
          <Icon.swap width="12" height="12"/>
          <span>Amounts look reversed</span>
        </button>
      </div>

      {activeTab === "output" ? (
        <div className="cc-preview__table-wrap">
          <table className="cc-table cc-table--mapped">
            <thead>
              <tr>
                {CC_COLUMNS.map(col => {
                  const meta = FIELD_META[col];
                  const mapped = Object.values(mapping).includes(col);
                  return (
                    <th key={col} className={`cc-th--${meta.tone} ${!mapped ? "cc-th--unmapped" : ""}`}>
                      <span className="cc-th__lbl">{col}</span>
                      {!mapped && <span className="cc-th__skip">skipped</span>}
                    </th>
                  );
                })}
              </tr>
            </thead>
            <tbody>
              {rows.map((row, ri) => {
                const converted = getConvertedRow(row, mapping, invertedOutflow);
                return (
                  <tr key={ri}>
                    {CC_COLUMNS.map((col, ci) => {
                      const val = converted[ci];
                      const isMoney = col === "Outflow" || col === "Inflow";
                      return (
                        <td key={col} className={`${isMoney ? "cc-td--num" : ""} cc-td--${FIELD_META[col].tone}`}>
                          {val ? (isMoney ? <><span>$</span><span>{val}</span></> : val)
                               : <span className="cc-td--empty">—</span>}
                        </td>
                      );
                    })}
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      ) : (
        <div className="cc-preview__table-wrap">
          <table className="cc-table cc-table--raw">
            <thead>
              <tr>{headers.map((h, i) => <th key={i}>{h}</th>)}</tr>
            </thead>
            <tbody>
              {rows.map((row, ri) => (
                <tr key={ri}>
                  {row.map((cell, ci) => (
                    <td key={ci} className={/^-?[\d,. ]+$/.test(cell) ? "cc-td--num" : ""}>
                      {cell || <span className="cc-td--empty">—</span>}
                    </td>
                  ))}
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      )}
    </section>
  );
}

// ---------- Toast ----------
function Toast({ msg, onClose }) {
  if (!msg) return null;
  return (
    <div className="cc-toast">
      <Icon.check width="14" height="14"/>
      <span>{msg}</span>
      <button className="cc-toast__x" onClick={onClose}><Icon.x width="12" height="12"/></button>
    </div>
  );
}

// ---------- App ----------
function App() {
  const [theme, setTheme] = useState(() => localStorage.getItem("cc-theme") || "dark");
  const [hasHeader, setHasHeader] = useState(true);
  const [hasFile, setHasFile] = useState(false);
  const [headers, setHeaders] = useState([]);
  const [rows, setRows] = useState([]);
  const [filename, setFilename] = useState("");
  const [mapping, setMapping] = useState({});
  const [invertedOutflow, setInvertedOutflow] = useState(false);
  const [toast, setToast] = useState("");
  const [rawText, setRawText] = useState(null);
  const [rawDelimiter, setRawDelimiter] = useState("auto");
  const [rawEncoding, setRawEncoding] = useState("UTF-8");

  useEffect(() => {
    document.documentElement.setAttribute("data-theme", theme);
    localStorage.setItem("cc-theme", theme);
  }, [theme]);

  const handleRawLoaded = useCallback((text, fname, delimiter, encoding) => {
    setRawText(text);
    setRawDelimiter(delimiter);
    setRawEncoding(encoding);
    setFilename(fname);
    const parsed = parseCSVText(text, { delimiter, hasHeader, startAtRow: 1 });
    setHeaders(parsed.headers);
    setRows(parsed.rows);
    setMapping(autoDetectMapping(parsed.headers));
    setInvertedOutflow(false);
    setHasFile(true);
  }, [hasHeader]);

  const handleToggleHeader = useCallback(() => {
    const newHdr = !hasHeader;
    setHasHeader(newHdr);
    if (rawText) {
      const parsed = parseCSVText(rawText, { delimiter: rawDelimiter, hasHeader: newHdr, startAtRow: 1 });
      setHeaders(parsed.headers);
      setRows(parsed.rows);
      setMapping(autoDetectMapping(parsed.headers));
    }
  }, [hasHeader, rawText, rawDelimiter]);

  const handleSample = useCallback(() => {
    setRawText(null);
    setHeaders(SAMPLE_HEADERS);
    setRows(SAMPLE_ROWS);
    setFilename(SAMPLE_FILE);
    setMapping({ ...SAMPLE_MAPPING });
    setInvertedOutflow(false);
    setHasFile(true);
  }, []);

  const handleReset = () => {
    setHasFile(false);
    setHeaders([]);
    setRows([]);
    setFilename("");
    setMapping({});
    setInvertedOutflow(false);
    setRawText(null);
  };

  const handleSave = () => {
    const csvText = buildConvertedCSV(headers, rows, mapping, invertedOutflow);
    const date = new Date();
    const dateStr = date.toISOString().split("T")[0].replace(/-/g, "");
    const a = document.createElement("a");
    a.href = "data:attachment/csv;charset=utf-8," + encodeURIComponent(csvText);
    a.download = `cashcoach_${dateStr}.csv`;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    setToast("Cash Coach CSV downloaded. Open it in the Cash Coach app to import.");
    setTimeout(() => setToast(""), 4500);
  };

  const handleChangeMapping = (ccCol, sourceIdx) => {
    const next = { ...mapping };
    Object.keys(next).forEach(k => { if (next[k] === ccCol) next[k] = ""; });
    if (sourceIdx !== "") next[String(sourceIdx)] = ccCol;
    setMapping(next);
  };

  const mappedCount = CC_COLUMNS.filter(col => Object.values(mapping).includes(col)).length;
  const dateRange = hasFile ? computeDateRange(rows, mapping) : "";

  return (
    <div className="cc-app">
      <Header
        theme={theme}
        onToggleTheme={() => setTheme(t => t === "dark" ? "light" : "dark")}
      />

      <main className="cc-main">
        {!hasFile ? (
          <DropZone
            hasHeader={hasHeader}
            onToggleHeader={handleToggleHeader}
            onRawLoaded={handleRawLoaded}
            onSample={handleSample}
          />
        ) : (
          <div className="cc-content cc-content--two-col">
            <MappingSidebar
              headers={headers}
              mapping={mapping}
              onChangeMapping={handleChangeMapping}
              filename={filename}
              totalRows={rows.length}
              dateRange={dateRange}
              mappedCount={mappedCount}
              onSave={handleSave}
              onLoadDifferent={handleReset}
              hasHeader={hasHeader}
              onToggleHeader={handleToggleHeader}
            />
            <PreviewPane
              headers={headers}
              rows={rows}
              mapping={mapping}
              invertedOutflow={invertedOutflow}
              onFlip={() => setInvertedOutflow(v => !v)}
            />
          </div>
        )}
      </main>

      <Toast msg={toast} onClose={() => setToast("")}/>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App/>);
