(function initValuationsUploadModule(global) {
  const VALUATIONS_UPLOAD_INPUT_MODE = {
    paste: 'paste',
    file: 'file'
  };

  function parseCsvLine(line) {
    const cells = [];
    let current = '';
    let inQuotes = false;

    for (let index = 0; index < line.length; index += 1) {
      const char = line[index];

      if (char === '"') {
        if (inQuotes && line[index + 1] === '"') {
          current += '"';
          index += 1;
        } else {
          inQuotes = !inQuotes;
        }
        continue;
      }

      if (char === ',' && !inQuotes) {
        cells.push(current);
        current = '';
        continue;
      }

      current += char;
    }

    cells.push(current);
    return cells;
  }

  function toNormalizedHeader(value) {
    return String(value || '').trim().toLowerCase();
  }

  function toMonthNumber(value) {
    const normalized = String(value || '').trim().toLowerCase();
    const monthMap = {
      jan: 1,
      january: 1,
      feb: 2,
      february: 2,
      mar: 3,
      march: 3,
      apr: 4,
      april: 4,
      may: 5,
      jun: 6,
      june: 6,
      jul: 7,
      july: 7,
      aug: 8,
      august: 8,
      sep: 9,
      sept: 9,
      september: 9,
      oct: 10,
      october: 10,
      nov: 11,
      november: 11,
      dec: 12,
      december: 12
    };

    return monthMap[normalized] || null;
  }

  function toIsoDateString(rawValue) {
    const normalizedValue = String(rawValue || '').trim().replace(/\//g, '-');
    if (!normalizedValue) {
      return null;
    }

    const ordinalDateMatch = normalizedValue.match(/^(\d{1,2})(?:st|nd|rd|th)?\s+([A-Za-z]+)\s+(\d{4})$/i);
    if (ordinalDateMatch) {
      const day = Number.parseInt(ordinalDateMatch[1], 10);
      const month = toMonthNumber(ordinalDateMatch[2]);
      const year = Number.parseInt(ordinalDateMatch[3], 10);

      if (Number.isFinite(day) && Number.isFinite(year) && month != null) {
        const utcDate = new Date(Date.UTC(year, month - 1, day));
        if (
          Number.isFinite(utcDate.getTime())
          && utcDate.getUTCFullYear() === year
          && utcDate.getUTCMonth() === month - 1
          && utcDate.getUTCDate() === day
        ) {
          return utcDate.toISOString().slice(0, 10);
        }
      }
    }

    const date = new Date(`${normalizedValue}T00:00:00.000Z`);
    if (!Number.isFinite(date.getTime())) {
      return null;
    }

    return date.toISOString().slice(0, 10);
  }

  function moveDateToNearestFriday(isoDate) {
    if (typeof isoDate !== 'string' || !isoDate) {
      return null;
    }

    const date = new Date(`${isoDate}T00:00:00.000Z`);
    if (!Number.isFinite(date.getTime())) {
      return null;
    }

    const day = date.getUTCDay();
    const movedDate = new Date(date);

    if (day >= 1 && day <= 4) {
      movedDate.setUTCDate(movedDate.getUTCDate() + (5 - day));
    } else if (day === 6) {
      movedDate.setUTCDate(movedDate.getUTCDate() - 1);
    } else if (day === 0) {
      movedDate.setUTCDate(movedDate.getUTCDate() - 2);
    }

    const nextIsoDate = movedDate.toISOString().slice(0, 10);
    return {
      date: nextIsoDate,
      moved: nextIsoDate !== isoDate
    };
  }

  function toNumberValue(rawValue) {
    const value = String(rawValue || '').trim();
    if (!value) {
      return null;
    }

    const parsed = Number.parseFloat(value.replace(/,/g, ''));
    return Number.isFinite(parsed) ? parsed : null;
  }

  function ValuationsUploadPanel(props) {
    const portfolios = Array.isArray(props.portfolios) ? props.portfolios : [];
    const navigate = typeof props.navigate === 'function' ? props.navigate : () => {};
    const getAuthorizedJsonHeaders = typeof props.getAuthorizedJsonHeaders === 'function'
      ? props.getAuthorizedJsonHeaders
      : (() => ({ Accept: 'application/json' }));
    const onUnauthorized = typeof props.onUnauthorized === 'function' ? props.onUnauthorized : () => {};
    const getValidationMessage = typeof props.getValidationMessage === 'function'
      ? props.getValidationMessage
      : ((message) => message || 'A Validation Issue occurred.');
    const formatDisplayDate = typeof props.formatDisplayDate === 'function'
      ? props.formatDisplayDate
      : (value) => (value == null || value === '' ? '-' : String(value));
    const formatAmount = typeof props.formatAmount === 'function'
      ? props.formatAmount
      : (value) => {
        const numeric = Number.parseFloat(value);
        return Number.isFinite(numeric) ? numeric.toFixed(2) : '0.00';
      };
    const onUploadingChange = typeof props.onUploadingChange === 'function'
      ? props.onUploadingChange
      : () => {};

    const [valuationsUploadMode, setValuationsUploadMode] = React.useState(VALUATIONS_UPLOAD_INPUT_MODE.paste);
    const [valuationsUploadText, setValuationsUploadText] = React.useState('');
    const [valuationsUploadFileName, setValuationsUploadFileName] = React.useState('');
    const [valuationsUploadRows, setValuationsUploadRows] = React.useState([]);
    const [valuationsUploadParseFailures, setValuationsUploadParseFailures] = React.useState([]);
    const [valuationsUploadColumns, setValuationsUploadColumns] = React.useState([]);
    const [valuationsUploadErrors, setValuationsUploadErrors] = React.useState([]);
    const [valuationsUploadSummary, setValuationsUploadSummary] = React.useState(null);
    const [valuationsUploadStatus, setValuationsUploadStatus] = React.useState(null);
    const [valuationsUploadDuplicates, setValuationsUploadDuplicates] = React.useState([]);
    const [valuationsUploadSucceeded, setValuationsUploadSucceeded] = React.useState(false);

    const valuationsUploadPastePlaceholder = React.useMemo(() => {
      const todayIsoDate = new Date().toISOString().slice(0, 10);
      const randomAmountSample = () => (10000 + (Math.random() * 90000)).toFixed(2);

      const validPortfolios = portfolios
        .map((portfolio) => ({
          id: String(portfolio && portfolio.id != null ? portfolio.id : '').trim(),
          name: String(portfolio && portfolio.name ? portfolio.name : '').trim()
        }))
        .filter((portfolio) => portfolio.id && portfolio.name);

      if (validPortfolios.length === 0) {
        return `Paste rows using portfolio names...\n\nDate,Portfolio Name\n${todayIsoDate},${randomAmountSample()}`;
      }

      const nameHeader = `Date,${validPortfolios.map((portfolio) => portfolio.name).join(',')}`;
      const sampleRowValues = validPortfolios.map(() => randomAmountSample()).join(',');
      const sampleRow = `${todayIsoDate},${sampleRowValues}`;

      return [
        'Paste rows using portfolio names...',
        '',
        nameHeader,
        sampleRow
      ].join('\n');
    }, [portfolios]);

    function clearValuationsUploadPreviewState() {
      setValuationsUploadText('');
      setValuationsUploadFileName('');
      setValuationsUploadRows([]);
      setValuationsUploadParseFailures([]);
      setValuationsUploadColumns([]);
      setValuationsUploadErrors([]);
      setValuationsUploadSummary(null);
      setValuationsUploadStatus(null);
      setValuationsUploadDuplicates([]);
      setValuationsUploadSucceeded(false);
    }

    function handleValuationsInputModeChange(nextMode) {
      if (nextMode === valuationsUploadMode) {
        return;
      }

      const hasPreview = valuationsUploadRows.length > 0 || valuationsUploadParseFailures.length > 0;
      if (hasPreview) {
        clearValuationsUploadPreviewState();
      }

      setValuationsUploadMode(nextMode);
    }

    const parseValuationsUploadContent = React.useCallback((rawText) => {
      const lines = String(rawText || '')
        .split(/\r?\n/)
        .map((line) => line.trim())
        .filter(Boolean);

      if (lines.length === 0) {
        setValuationsUploadRows([]);
        setValuationsUploadParseFailures([]);
        setValuationsUploadColumns([]);
        setValuationsUploadErrors([]);
        setValuationsUploadSummary(null);
        return;
      }

      const headers = parseCsvLine(lines[0]).map((cell) => String(cell || '').trim());
      const normalizedHeaders = headers.map((cell) => toNormalizedHeader(cell));
      const dateColumnIndex = normalizedHeaders.findIndex((header) => header === 'date');

      const nameToPortfolio = new Map(portfolios.map((portfolio) => [
        String(portfolio && portfolio.name || '').trim().toLowerCase(),
        portfolio
      ]));

      const portfolioColumns = [];
      for (let index = 0; index < normalizedHeaders.length; index += 1) {
        if (index === dateColumnIndex) {
          continue;
        }

        const normalizedHeader = normalizedHeaders[index];
        if (!normalizedHeader) {
          continue;
        }

        const portfolio = nameToPortfolio.get(normalizedHeader);

        if (portfolio) {
          portfolioColumns.push({
            columnIndex: index,
            portfolioId: Number.parseInt(portfolio.id, 10),
            portfolioName: portfolio.name
          });
        }
      }

      const nextErrors = [];
      if (dateColumnIndex < 0) {
        nextErrors.push('Missing mandatory column: Date.');
      }
      if (portfolioColumns.length === 0) {
        nextErrors.push('Missing mandatory portfolio columns: include at least one valid Portfolio Name column.');
      }

      const unmatchedColumns = headers
        .filter((header, index) => index !== dateColumnIndex && String(header || '').trim())
        .filter((header) => {
          const normalized = toNormalizedHeader(header);
          return !nameToPortfolio.has(normalized);
        });
      if (unmatchedColumns.length > 0) {
        nextErrors.push(`Unmatched columns ignored: ${unmatchedColumns.join(', ')}`);
      }

      if (nextErrors.length > 0) {
        setValuationsUploadRows([]);
        setValuationsUploadParseFailures(lines.slice(1));
        setValuationsUploadColumns([]);
        setValuationsUploadErrors(nextErrors);
        setValuationsUploadSummary({ validCount: 0, movedCount: 0 });
        return;
      }

      const parsedRows = [];
      const parseFailures = [];
      let movedCount = 0;
      const seenPortfolioDateKeys = new Set();

      for (const rawLine of lines.slice(1)) {
        const cells = parseCsvLine(rawLine).map((cell) => String(cell || '').trim());

        const rawDate = cells[dateColumnIndex] || '';
        const normalizedDate = toIsoDateString(rawDate);
        const movedDateInfo = moveDateToNearestFriday(normalizedDate);
        if (!movedDateInfo) {
          parseFailures.push(rawLine);
          continue;
        }

        const values = {};
        let hasValue = false;
        let rowInvalid = false;

        for (const column of portfolioColumns) {
          const amount = toNumberValue(cells[column.columnIndex]);
          if (amount == null) {
            continue;
          }

          if (!Number.isFinite(amount)) {
            rowInvalid = true;
            break;
          }

          hasValue = true;
          values[String(column.portfolioId)] = amount;
        }

        if (rowInvalid || !hasValue) {
          parseFailures.push(rawLine);
          continue;
        }

        const adjustedDate = movedDateInfo.date;
        const portfolioIdsInRow = Object.keys(values);
        const hasDuplicatePortfolioDate = portfolioIdsInRow.some((portfolioId) => seenPortfolioDateKeys.has(`${portfolioId}|${adjustedDate}`));
        if (hasDuplicatePortfolioDate) {
          parseFailures.push(rawLine);
          continue;
        }

        portfolioIdsInRow.forEach((portfolioId) => {
          seenPortfolioDateKeys.add(`${portfolioId}|${adjustedDate}`);
        });

        if (movedDateInfo.moved) {
          movedCount += 1;
        }

        parsedRows.push({
          rawLine,
          originalDate: normalizedDate,
          adjustedDate: movedDateInfo.date,
          dateMoved: movedDateInfo.moved,
          values
        });
      }

      parsedRows.sort((left, right) => left.adjustedDate.localeCompare(right.adjustedDate));

      setValuationsUploadRows(parsedRows);
      setValuationsUploadParseFailures(parseFailures);
      setValuationsUploadColumns(portfolioColumns);
      setValuationsUploadErrors([]);
      setValuationsUploadSummary({
        validCount: parsedRows.length,
        movedCount
      });
    }, [portfolios]);

    const handleValuationsUploadTextChange = (event) => {
      const nextValue = event.target.value;
      setValuationsUploadText(nextValue);
      setValuationsUploadFileName('');
      setValuationsUploadStatus(null);
      setValuationsUploadDuplicates([]);
      setValuationsUploadSucceeded(false);
      parseValuationsUploadContent(nextValue);
    };

    const handleValuationsFileSelected = (event) => {
      const file = event.target.files && event.target.files[0] ? event.target.files[0] : null;
      if (!file) {
        return;
      }

      const reader = new FileReader();
      reader.onload = () => {
        const fileText = String(reader.result || '');
        setValuationsUploadFileName(file.name);
        setValuationsUploadText(fileText);
        setValuationsUploadStatus(null);
        setValuationsUploadDuplicates([]);
        setValuationsUploadSucceeded(false);
        parseValuationsUploadContent(fileText);
      };
      reader.onerror = () => {
        setValuationsUploadStatus({
          type: 'error',
          message: 'Could not read selected file.'
        });
      };
      reader.readAsText(file);
    };

    const handleDraftUploadValuations = async () => {
      if (valuationsUploadRows.length === 0) {
        return;
      }

      onUploadingChange(true);
      setValuationsUploadStatus(null);
      setValuationsUploadDuplicates([]);
      setValuationsUploadSucceeded(false);

      const payload = {
        portfolioIds: valuationsUploadColumns.map((column) => column.portfolioId),
        rows: valuationsUploadRows.map((row) => ({
          date: row.adjustedDate,
          values: row.values
        }))
      };

      try {
        const response = await fetch('/api/valuations', {
          method: 'POST',
          headers: Object.assign({ 'Content-Type': 'application/json' }, getAuthorizedJsonHeaders()),
          body: JSON.stringify(payload)
        });

        if (response.status === 401 || response.status === 403) {
          onUnauthorized();
          return;
        }

        const responsePayload = await response.json().catch(() => ({}));
        if (!response.ok) {
          const errorCode = responsePayload && responsePayload.code != null ? responsePayload.code : 'Unknown';
          if (response.status === 422) {
            setValuationsUploadStatus({
              type: 'error',
              message: getValidationMessage('A Validation Issue occurred.', errorCode)
            });
            return;
          }

          setValuationsUploadStatus({
            type: 'error',
            message: getValidationMessage('A Technical Error occurred.', errorCode)
          });
          return;
        }

        const insertedCount = Number.isFinite(responsePayload.insertedCount) ? responsePayload.insertedCount : 0;
        const updatedCount = Number.isFinite(responsePayload.updatedCount) ? responsePayload.updatedCount : 0;
        const duplicates = Array.isArray(responsePayload.duplicates) ? responsePayload.duplicates : [];
        setValuationsUploadDuplicates(duplicates);

        setValuationsUploadStatus({
          type: 'success',
          message: `Upload complete. Inserted ${insertedCount} valuations. Updated ${updatedCount} valuations.`
        });
        setValuationsUploadSucceeded(true);
      } catch (error) {
        setValuationsUploadStatus({
          type: 'error',
          message: 'A Technical Error occurred while uploading valuations.'
        });
        setValuationsUploadSucceeded(false);
      } finally {
        onUploadingChange(false);
      }
    };

    return (
      <section className="panel-card valuations-panel-card upload-panel">
        <div className="valuations-header-row">
          <h3 style={{ marginTop: 0, marginBottom: 0 }}>Upload Valuations</h3>
          <button type="button" className="secondary-btn" onClick={() => navigate('/valuations')}>Back to Valuations</button>
        </div>

        <div className="upload-warning-box">
          <ul>
            <li>Only end of week valuations are supported.</li>
            <li>Dates are moved to the closest Friday.</li>
            <li>Monday to Thursday move forward; Saturday and Sunday move back.</li>
            <li>Any existing valuations for the same dates will be overwritten.</li>
          </ul>
        </div>

        <div className="guidance">
          <p className="muted upload-note">Paste valuation data in CSV format with a Date column and one column per Portfolio.</p>
          <p className="muted upload-note">Include a header row using portfolio names.</p>
          <p className="muted upload-note">For best results use ISO formatted dates: <b>YYYY-MM-DD</b>.</p>
          <p className="muted upload-note">The dates will be adjusted to the closest Friday and values should be in each portfolio base currency.</p>
        </div>

        <div className="upload-input-mode">
          <button
            type="button"
            className={valuationsUploadMode === VALUATIONS_UPLOAD_INPUT_MODE.paste ? 'primary-btn' : 'secondary-btn'}
            onClick={() => handleValuationsInputModeChange(VALUATIONS_UPLOAD_INPUT_MODE.paste)}
          >
            Paste Data
          </button>
          <button
            type="button"
            className={valuationsUploadMode === VALUATIONS_UPLOAD_INPUT_MODE.file ? 'primary-btn' : 'secondary-btn'}
            onClick={() => handleValuationsInputModeChange(VALUATIONS_UPLOAD_INPUT_MODE.file)}
          >
            Upload File
          </button>
        </div>

        {valuationsUploadMode === VALUATIONS_UPLOAD_INPUT_MODE.paste ? (
          <label className="field">
            <span>Paste Data (Clipboard)</span>
            <textarea
              className="upload-textarea valuations-upload-textarea"
              value={valuationsUploadText}
              onChange={handleValuationsUploadTextChange}
              placeholder={valuationsUploadPastePlaceholder}
            />
          </label>
        ) : (
          <label className="field">
            <span>Upload a File</span>
            <input type="file" accept=".csv,text/csv" onChange={handleValuationsFileSelected} />
            {valuationsUploadFileName ? <span className="muted">Loaded file: {valuationsUploadFileName}</span> : null}
          </label>
        )}

        {valuationsUploadErrors.length > 0 ? (
          <div className="status-message error" role="status">
            <strong>Validation</strong>
            <ul>
              {valuationsUploadErrors.map((errorMessage) => (
                <li key={errorMessage}>{errorMessage}</li>
              ))}
            </ul>
          </div>
        ) : null}

        {valuationsUploadRows.length > 0 ? (
          <div className="upload-table-wrap">
            <table className="transactions-table">
              <thead>
                <tr>
                  <th>Date</th>
                  {valuationsUploadColumns.map((column) => (
                    <th key={column.portfolioId} className="txn-cell-amount">{column.portfolioName}</th>
                  ))}
                  <th className="txn-cell-amount">Total</th>
                </tr>
              </thead>
              <tbody>
                  {valuationsUploadRows.map((row) => {
                  const rowTotal = valuationsUploadColumns
                    .map((column) => row.values[String(column.portfolioId)])
                    .filter((value) => Number.isFinite(value))
                    .reduce((acc, value) => acc + value, 0);
                  const totalClassName = rowTotal > 0
                    ? 'valuations-total-positive'
                    : (rowTotal < 0 ? 'valuations-total-negative' : '');

                  return (
                    <tr key={`${row.adjustedDate}-${row.rawLine}`} className={row.dateMoved ? 'valuations-moved-date-row' : ''}>
                      <td>
                        {formatDisplayDate(row.adjustedDate)}
                        {row.dateMoved ? <span className="muted valuations-moved-date-note"> (moved from {formatDisplayDate(row.originalDate)})</span> : null}
                      </td>
                      {valuationsUploadColumns.map((column) => {
                        const value = row.values[String(column.portfolioId)];
                        return <td key={`${row.rawLine}-${column.portfolioId}`} className="txn-cell-amount">{value == null ? '-' : formatAmount(value)}</td>;
                      })}
                      <td className={`txn-cell-amount ${totalClassName}`.trim()}>{formatAmount(rowTotal)}</td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
        ) : null}

        {valuationsUploadSummary ? (
          <React.Fragment>
            <p className="upload-combined-legend">
              <span className="upload-combined-legend-swatch valuations-moved-date-swatch" />
              <span>
                Moved to closest Friday: {valuationsUploadSummary.movedCount}.
              </span>
            </p>
            <div className="status-message success" role="status">
              <span>{valuationsUploadSummary.validCount} rows ready to upload.</span>
            </div>
          </React.Fragment>
        ) : null}

        {valuationsUploadParseFailures.length > 0 ? (
          <div className="upload-parse-failures">
            <strong>Rows that could not be parsed</strong>
            <pre className="upload-parse-failures-raw">{valuationsUploadParseFailures.join('\n')}</pre>
          </div>
        ) : null}

        {valuationsUploadStatus ? (
          <div className={`status-message ${valuationsUploadStatus.type}`} role="status">
            <span>{valuationsUploadStatus.message}</span>
          </div>
        ) : null}

        {valuationsUploadDuplicates.length > 0 ? (
          <section className="upload-duplicates-preview">
            <h4>Duplicate Valuations ({valuationsUploadDuplicates.length})</h4>
            <div className="upload-table-wrap">
              <table className="transactions-table">
                <thead>
                  <tr>
                    <th>Date</th>
                    <th>Portfolio</th>
                    <th className="txn-cell-amount">Value</th>
                  </tr>
                </thead>
                <tbody>
                  {valuationsUploadDuplicates.map((duplicate, index) => {
                    const duplicatePortfolio = valuationsUploadColumns.find((column) => (
                      column.portfolioId === Number.parseInt(duplicate.portfolioId, 10)
                    ));

                    const displayValue = duplicate.value;

                    return (
                      <tr key={`${duplicate.portfolioId || 'portfolio'}-${duplicate.date || 'date'}-${index}`}>
                        <td>{formatDisplayDate(duplicate.date)}</td>
                        <td>{duplicatePortfolio ? duplicatePortfolio.portfolioName : '-'}</td>
                        <td className={`txn-cell-amount ${displayValue < 0 ? 'txn-cell-amount-negative' : (displayValue > 0 ? 'txn-cell-amount-positive' : '')}`.trim()}>
                          {Number.isFinite(displayValue) ? formatAmount(displayValue) : '-'}
                        </td>
                      </tr>
                    );
                  })}
                </tbody>
              </table>
            </div>
          </section>
        ) : null}

        {valuationsUploadRows.length > 0 && !valuationsUploadSucceeded ? (
          <div className="hero-actions upload-hero-actions">
            <button type="button" className="primary-btn" onClick={handleDraftUploadValuations}>
              Upload Valuations
            </button>
          </div>
        ) : null}
      </section>
    );
  }

  global.PortfolioValuationsUpload = {
    ValuationsUploadPanel
  };
})(window);
