import type { DocumentItem, DocumentTax, Estimate, Invoice, RecurringInvoice, TaxType } from './shared';
import { useEffect, useMemo, useState } from 'react';
import { Plus, Trash2, X } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { Input } from '@/components/ui/input';
import { Textarea } from '@/components/ui/textarea';
import { ErrorState, LoadingState } from './components';
import { formatMoney, fromMajor, toInputDate, toMajor } from './format';
import { FREQUENCY_PRESETS } from './shared';
import {
  useCurrency,
  useLookups,
  useNextNumber,
  useResourceDetail,
  useResourceMutations,
  useTemplates,
} from './useInvoiceShelfData';

// The invoice / estimate / recurring-invoice form. One component for all three: they share every hard part
// — the line-item table, the discounts, the taxes and the totals — and differ only in a header strip.
// An invoice has a number and two dates; an estimate the same with different labels; a recurring invoice
// has no number at all (each generated invoice takes the next one) and carries a schedule instead of dates.
//
// ── the arithmetic ───────────────────────────────────────────────────────────────────────────────
// Ported from upstream's `use-document-calculations.ts`, and the rounding points are load-bearing because
// the server re-validates the totals we send:
//
//   line sub_total   = round(price × quantity)                      (price already in minor units)
//   line discount    = percentage → round(|sub_total| × d / 100)
//                      fixed      → min(round(d × 100), |sub_total|)   ← d is entered in MAJOR units
//   line total       = sub_total − discount
//   document sub_total = Σ line totals
//   tax base           = document sub_total − document discount
//   compound tax base  = tax base + simple taxes
//   total              = tax_included ? tax base : tax base + total tax
//
// Every value posted is an integer of minor units. Amounts are shown to the user in major units and
// converted at the edges only — a float never survives past a keystroke.

type Resource = 'invoices' | 'estimates' | 'recurring-invoices';

type Line = {
  key: string;
  item_id: number | null;
  name: string;
  description: string;
  quantity: number;
  /** Minor units. */
  price: number;
  discount: number;
  discount_type: 'fixed' | 'percentage';
  tax_type_ids: number[];
};

type Props = { resource: Resource; id: number | 'new'; onClose: () => void };

const KIND_NOUN: Record<Resource, string> = {
  invoices: 'invoice',
  estimates: 'estimate',
  'recurring-invoices': 'recurring invoice',
};

const newLine = (index: number): Line => ({
  key: `new-${index}`,
  item_id: null,
  name: '',
  description: '',
  quantity: 1,
  price: 0,
  discount: 0,
  discount_type: 'fixed',
  tax_type_ids: [],
});

export const DocumentEditor = ({ resource, id, onClose }: Props) => {
  const isNew = id === 'new';
  const isRecurring = resource === 'recurring-invoices';
  const isInvoice = resource === 'invoices';
  const currency = useCurrency();
  const { customers, items, taxTypes } = useLookups();
  // A recurring invoice renders through the invoice templates — it has no gallery of its own.
  const templates = useTemplates(isRecurring ? 'invoices' : resource);
  const nextNumber = useNextNumber(isInvoice ? 'invoice' : 'estimate', isNew && !isRecurring);
  const { record, isLoading, error } = useResourceDetail<Invoice & Estimate & RecurringInvoice>(
    resource,
    isNew ? null : (id as number),
  );
  const { create, update } = useResourceMutations(resource, isRecurring ? 'recurring invoices' : resource);

  const [customerId, setCustomerId] = useState<number | null>(null);
  const [number, setNumber] = useState('');
  const [primaryDate, setPrimaryDate] = useState(() => toInputDate(new Date().toISOString()));
  const [secondaryDate, setSecondaryDate] = useState('');
  const [reference, setReference] = useState('');
  const [template, setTemplate] = useState('');
  const [notes, setNotes] = useState('');
  const [lines, setLines] = useState<Line[]>([newLine(0)]);
  const [discount, setDiscount] = useState(0);
  const [discountType, setDiscountType] = useState<'fixed' | 'percentage'>('fixed');
  const [docTaxIds, setDocTaxIds] = useState<number[]>([]);
  const [taxPerItem, setTaxPerItem] = useState<'YES' | 'NO'>('NO');
  const [taxIncluded, setTaxIncluded] = useState(false);
  const [loaded, setLoaded] = useState(false);

  // Recurring-only. `frequency` is a raw cron expression upstream; a schedule authored outside the preset
  // list keeps its own expression and is offered back as an extra option rather than silently rewritten.
  const [frequency, setFrequency] = useState<string>(FREQUENCY_PRESETS[2].cron);
  const [limitBy, setLimitBy] = useState<'NONE' | 'COUNT' | 'DATE'>('NONE');
  const [limitCount, setLimitCount] = useState(1);
  const [limitDate, setLimitDate] = useState('');
  const [sendAutomatically, setSendAutomatically] = useState(false);
  const [recurringStatus, setRecurringStatus] = useState<'ACTIVE' | 'ON_HOLD'>('ACTIVE');

  // Seed the form once, from either the loaded record or the reserved next number. Guarded by `loaded`
  // rather than by a dependency list: react-query re-delivers `record` on every background refetch, and
  // without the guard a refetch mid-edit would silently discard what the user had typed.
  useEffect(() => {
    if (loaded) return;

    if (isNew) {
      // A recurring invoice has no number of its own, so it does not wait on one.
      if (!isRecurring && !nextNumber) return;
      if (nextNumber) setNumber(nextNumber);
      setTemplate(templates[0]?.name ?? (isInvoice || isRecurring ? 'invoice1' : 'estimate1'));
      setLoaded(true);
      return;
    }

    if (!record) return;
    setCustomerId(record.customer_id ?? null);
    setNumber(isInvoice ? (record.invoice_number ?? '') : (record.estimate_number ?? ''));
    if (isRecurring) {
      setPrimaryDate(toInputDate(record.starts_at));
      setFrequency(record.frequency ?? FREQUENCY_PRESETS[2].cron);
      setLimitBy(record.limit_by === 'COUNT' || record.limit_by === 'DATE' ? record.limit_by : 'NONE');
      setLimitCount(record.limit_count ?? 1);
      setLimitDate(toInputDate(record.limit_date));
      setSendAutomatically(Boolean(record.send_automatically));
      setRecurringStatus(record.status === 'ON_HOLD' ? 'ON_HOLD' : 'ACTIVE');
    } else {
      setPrimaryDate(toInputDate(isInvoice ? record.invoice_date : record.estimate_date));
      setSecondaryDate(toInputDate(isInvoice ? record.due_date : record.expiry_date));
    }
    setReference(record.reference_number ?? '');
    setTemplate(record.template_name ?? templates[0]?.name ?? '');
    setNotes(record.notes ?? '');
    setTaxPerItem(record.tax_per_item === 'YES' ? 'YES' : 'NO');
    setTaxIncluded(Boolean((record as { tax_included?: boolean }).tax_included));
    setDiscountType(record.discount_type === 'percentage' ? 'percentage' : 'fixed');
    // A fixed document discount is stored in minor units but entered in major ones; a percentage is a
    // plain number either way.
    setDiscount(record.discount_type === 'percentage' ? Number(record.discount) : toMajor(record.discount_val));
    setDocTaxIds((record.taxes ?? []).map((t) => t.tax_type_id).filter((t): t is number => t != null));
    setLines(
      (record.items ?? []).map((line, i) => ({
        key: `row-${line.id ?? i}`,
        item_id: line.item_id ?? null,
        name: line.name,
        description: line.description ?? '',
        quantity: Number(line.quantity) || 0,
        price: Number(line.price) || 0,
        discount: line.discount_type === 'percentage' ? Number(line.discount) : toMajor(line.discount_val),
        discount_type: line.discount_type === 'percentage' ? 'percentage' : 'fixed',
        tax_type_ids: (line.taxes ?? []).map((t) => t.tax_type_id).filter((t): t is number => t != null),
      })),
    );
    setLoaded(true);
  }, [record, isNew, nextNumber, templates, isInvoice, isRecurring, loaded]);

  const totals = useMemo(
    () => computeTotals({ lines, discount, discountType, docTaxIds, taxPerItem, taxIncluded, taxTypes }),
    [lines, discount, discountType, docTaxIds, taxPerItem, taxIncluded, taxTypes],
  );

  if (error) return <ErrorState error={error} />;
  if (!isNew && isLoading) return <LoadingState />;

  const patchLine = (key: string, changes: Partial<Line>) =>
    setLines((prev) => prev.map((l) => (l.key === key ? { ...l, ...changes } : l)));

  const pickItem = (key: string, itemId: number) => {
    const source = items.find((i) => i.id === itemId);
    if (!source) return;
    patchLine(key, {
      item_id: source.id,
      name: source.name,
      description: source.description ?? '',
      price: Number(source.price) || 0,
    });
  };

  const canSave =
    customerId != null &&
    (isRecurring || number.trim() !== '') &&
    primaryDate !== '' &&
    template !== '' &&
    lines.some((l) => l.name.trim());

  const save = () => {
    // The three resources agree on everything below the header, and disagree on nothing but it.
    const header = isRecurring
      ? {
          starts_at: primaryDate,
          frequency,
          limit_by: limitBy,
          limit_count: limitBy === 'COUNT' ? limitCount : null,
          limit_date: limitBy === 'DATE' ? limitDate : null,
          send_automatically: sendAutomatically,
          status: recurringStatus,
        }
      : {
          [isInvoice ? 'invoice_date' : 'estimate_date']: primaryDate,
          [isInvoice ? 'due_date' : 'expiry_date']: secondaryDate || null,
          [isInvoice ? 'invoice_number' : 'estimate_number']: number.trim(),
        };

    const payload = {
      ...header,
      customer_id: customerId,
      reference_number: reference || null,
      template_name: template,
      notes: notes || null,
      currency_id: record?.currency_id ?? currency?.id ?? null,
      exchange_rate: 1,
      tax_per_item: taxPerItem,
      discount_per_item: lines.some((l) => l.discount) ? 'YES' : 'NO',
      // The raw figure the user typed; `discount_val` alongside it is the resolved minor-unit amount.
      discount,
      discount_type: discountType,
      discount_val: totals.documentDiscount,
      sub_total: totals.subTotal,
      total: totals.total,
      tax: totals.totalTax,
      taxes: taxPerItem === 'NO' ? docTaxIds.map((tid) => taxLine(tid, taxTypes, totals.taxBase)) : [],
      items: lines
        .filter((l) => l.name.trim())
        .map((l) => {
          const line = computeLine(l, taxTypes, taxPerItem);
          return {
            item_id: l.item_id,
            name: l.name.trim(),
            description: l.description || null,
            quantity: l.quantity,
            price: l.price,
            discount: l.discount,
            discount_type: l.discount_type,
            discount_val: line.discountVal,
            tax: line.tax,
            total: line.total,
            taxes: taxPerItem === 'YES' ? l.tax_type_ids.map((tid) => taxLine(tid, taxTypes, line.total)) : [],
          };
        }),
    };

    if (isNew) create.mutate(payload, { onSuccess: onClose });
    else update.mutate({ id: id as number, payload }, { onSuccess: onClose });
  };

  const busy = create.isPending || update.isPending;

  return (
    <div className="flex h-full min-h-0 flex-col bg-background">
      <div className="flex shrink-0 items-center gap-2 border-b border-border px-3 py-2">
        <div className="min-w-0 flex-1 text-xs font-semibold">
          {isNew ? `New ${KIND_NOUN[resource]}` : `Edit ${number || KIND_NOUN[resource]}`}
        </div>
        <Button variant="ghost" size="sm" className="h-7 text-xs" onClick={onClose} disabled={busy}>
          Cancel
        </Button>
        <Button size="sm" className="h-7 text-xs" onClick={save} disabled={!canSave || busy}>
          {busy ? 'Saving…' : 'Save'}
        </Button>
        <Button variant="ghost" size="sm" className="h-7 px-2" onClick={onClose} title="Close">
          <X className="h-3.5 w-3.5" />
        </Button>
      </div>

      <div className="min-h-0 flex-1 overflow-y-auto p-3">
        <div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
          <Labelled label="Customer">
            <select
              value={customerId ?? ''}
              onChange={(ev) => setCustomerId(ev.target.value ? Number(ev.target.value) : null)}
              className="h-8 w-full rounded-md border border-input bg-background px-2 text-xs"
            >
              <option value="">Select…</option>
              {customers.map((c) => (
                <option key={c.id} value={c.id}>
                  {c.name}
                </option>
              ))}
            </select>
          </Labelled>
          {!isRecurring && (
            <Labelled label={isInvoice ? 'Invoice number' : 'Estimate number'}>
              <Input value={number} onChange={(ev) => setNumber(ev.target.value)} className="h-8 text-xs" />
            </Labelled>
          )}
          <Labelled label={isRecurring ? 'Starts on' : isInvoice ? 'Invoice date' : 'Estimate date'}>
            <Input
              type="date"
              value={primaryDate}
              onChange={(ev) => setPrimaryDate(ev.target.value)}
              className="h-8 text-xs"
            />
          </Labelled>
          {!isRecurring && (
            <Labelled label={isInvoice ? 'Due date' : 'Expiry date'}>
              <Input
                type="date"
                value={secondaryDate}
                onChange={(ev) => setSecondaryDate(ev.target.value)}
                className="h-8 text-xs"
              />
            </Labelled>
          )}

          {isRecurring && (
            <>
              <Labelled label="Repeats">
                <select
                  value={frequency}
                  onChange={(ev) => setFrequency(ev.target.value)}
                  className="h-8 w-full rounded-md border border-input bg-background px-2 text-xs"
                >
                  {FREQUENCY_PRESETS.map((f) => (
                    <option key={f.cron} value={f.cron}>
                      {f.label}
                    </option>
                  ))}
                  {!FREQUENCY_PRESETS.some((f) => f.cron === frequency) && (
                    <option value={frequency}>Custom ({frequency})</option>
                  )}
                </select>
              </Labelled>
              <Labelled label="Ends">
                <select
                  value={limitBy}
                  onChange={(ev) => setLimitBy(ev.target.value as 'NONE' | 'COUNT' | 'DATE')}
                  className="h-8 w-full rounded-md border border-input bg-background px-2 text-xs"
                >
                  <option value="NONE">Never</option>
                  <option value="COUNT">After a number of invoices</option>
                  <option value="DATE">On a date</option>
                </select>
              </Labelled>
              {limitBy === 'COUNT' && (
                <Labelled label="How many">
                  <Input
                    type="number"
                    min={1}
                    value={limitCount}
                    onChange={(ev) => setLimitCount(Number(ev.target.value))}
                    className="h-8 text-xs"
                  />
                </Labelled>
              )}
              {limitBy === 'DATE' && (
                <Labelled label="Until">
                  <Input
                    type="date"
                    value={limitDate}
                    onChange={(ev) => setLimitDate(ev.target.value)}
                    className="h-8 text-xs"
                  />
                </Labelled>
              )}
              <Labelled label="Status">
                <select
                  value={recurringStatus}
                  onChange={(ev) => setRecurringStatus(ev.target.value as 'ACTIVE' | 'ON_HOLD')}
                  className="h-8 w-full rounded-md border border-input bg-background px-2 text-xs"
                >
                  <option value="ACTIVE">Active</option>
                  <option value="ON_HOLD">On hold</option>
                </select>
              </Labelled>
            </>
          )}
          <Labelled label="Reference">
            <Input value={reference} onChange={(ev) => setReference(ev.target.value)} className="h-8 text-xs" />
          </Labelled>
          <Labelled label="Template">
            {/* Only the names: each template also carries a `path` that is a full base64-encoded PNG
                preview, several hundred KB apiece, and nothing here needs to render one. */}
            <select
              value={template}
              onChange={(ev) => setTemplate(ev.target.value)}
              className="h-8 w-full rounded-md border border-input bg-background px-2 text-xs"
            >
              {templates.map((t) => (
                <option key={t.name} value={t.name}>
                  {t.name}
                </option>
              ))}
              {template && !templates.some((t) => t.name === template) && <option value={template}>{template}</option>}
            </select>
          </Labelled>
          <Labelled label="Tax">
            <select
              value={taxPerItem}
              onChange={(ev) => setTaxPerItem(ev.target.value as 'YES' | 'NO')}
              className="h-8 w-full rounded-md border border-input bg-background px-2 text-xs"
            >
              <option value="NO">On the whole document</option>
              <option value="YES">Per line</option>
            </select>
          </Labelled>
          <Labelled label="Prices">
            <select
              value={taxIncluded ? 'incl' : 'excl'}
              onChange={(ev) => setTaxIncluded(ev.target.value === 'incl')}
              className="h-8 w-full rounded-md border border-input bg-background px-2 text-xs"
            >
              <option value="excl">Tax exclusive</option>
              <option value="incl">Tax inclusive</option>
            </select>
          </Labelled>
        </div>

        {isRecurring && (
          <label className="mt-3 flex items-center gap-2 text-xs">
            <Checkbox
              checked={sendAutomatically}
              onCheckedChange={(checked) => setSendAutomatically(checked === true)}
            />
            Email each generated invoice to the customer automatically
          </label>
        )}

        <div className="mt-4 rounded-lg border border-border">
          <div className="grid grid-cols-[1fr_70px_110px_110px_110px_32px] items-center gap-2 border-b border-border px-2 py-1.5 text-[10px] font-medium uppercase tracking-wide text-muted-foreground">
            <span>Item</span>
            <span className="text-right">Qty</span>
            <span className="text-right">Price</span>
            <span className="text-right">Discount</span>
            <span className="text-right">Total</span>
            <span />
          </div>

          {lines.map((line) => {
            const computed = computeLine(line, taxTypes, taxPerItem);
            return (
              <div key={line.key} className="border-b border-border/40 px-2 py-2">
                <div className="grid grid-cols-[1fr_70px_110px_110px_110px_32px] items-start gap-2">
                  <div className="min-w-0 space-y-1">
                    <div className="flex gap-1">
                      <Input
                        value={line.name}
                        onChange={(ev) => patchLine(line.key, { name: ev.target.value, item_id: null })}
                        placeholder="Description of work"
                        className="h-8 text-xs"
                      />
                      <select
                        value=""
                        onChange={(ev) => ev.target.value && pickItem(line.key, Number(ev.target.value))}
                        title="Fill from the item catalogue"
                        className="h-8 w-8 shrink-0 rounded-md border border-input bg-background text-xs"
                      >
                        <option value="">＋</option>
                        {items.map((i) => (
                          <option key={i.id} value={i.id}>
                            {i.name}
                          </option>
                        ))}
                      </select>
                    </div>
                    <Input
                      value={line.description}
                      onChange={(ev) => patchLine(line.key, { description: ev.target.value })}
                      placeholder="Notes (optional)"
                      className="h-7 text-[11px]"
                    />
                    {taxPerItem === 'YES' && (
                      <TaxPicker
                        taxTypes={taxTypes}
                        selected={line.tax_type_ids}
                        onChange={(ids) => patchLine(line.key, { tax_type_ids: ids })}
                      />
                    )}
                  </div>

                  <Input
                    type="number"
                    value={line.quantity}
                    onChange={(ev) => patchLine(line.key, { quantity: Number(ev.target.value) })}
                    className="h-8 text-right text-xs"
                  />
                  <Input
                    type="number"
                    step="0.01"
                    value={toMajor(line.price)}
                    onChange={(ev) => patchLine(line.key, { price: fromMajor(ev.target.value) })}
                    className="h-8 text-right text-xs"
                  />
                  <div className="flex gap-1">
                    <Input
                      type="number"
                      step="0.01"
                      value={line.discount}
                      onChange={(ev) => patchLine(line.key, { discount: Number(ev.target.value) })}
                      className="h-8 text-right text-xs"
                    />
                    <select
                      value={line.discount_type}
                      onChange={(ev) =>
                        patchLine(line.key, { discount_type: ev.target.value as 'fixed' | 'percentage' })
                      }
                      className="h-8 w-9 shrink-0 rounded-md border border-input bg-background text-xs"
                    >
                      <option value="fixed">{currency?.symbol ?? '#'}</option>
                      <option value="percentage">%</option>
                    </select>
                  </div>
                  <div className="pt-2 text-right text-xs tabular-nums">{formatMoney(computed.total, currency)}</div>
                  <Button
                    variant="ghost"
                    size="sm"
                    className="h-8 w-8 p-0 text-muted-foreground"
                    disabled={lines.length === 1}
                    onClick={() => setLines((prev) => prev.filter((l) => l.key !== line.key))}
                  >
                    <Trash2 className="h-3.5 w-3.5" />
                  </Button>
                </div>
              </div>
            );
          })}

          <div className="px-2 py-2">
            <Button
              variant="ghost"
              size="sm"
              className="h-7 text-xs"
              onClick={() => setLines((prev) => [...prev, newLine(prev.length)])}
            >
              <Plus className="mr-1.5 h-3.5 w-3.5" />
              Add line
            </Button>
          </div>
        </div>

        <div className="mt-4 grid gap-3 lg:grid-cols-2">
          <div>
            <Labelled label="Notes">
              <Textarea
                value={notes}
                onChange={(ev) => setNotes(ev.target.value)}
                rows={6}
                className="text-xs"
                placeholder="Shown on the document."
              />
            </Labelled>
          </div>

          <div className="rounded-lg border border-border p-3">
            <TotalRow label="Subtotal" value={formatMoney(totals.subTotal, currency)} />

            <div className="flex items-center justify-between gap-2 py-1 text-xs">
              <span className="text-muted-foreground">Discount</span>
              <div className="flex items-center gap-1">
                <Input
                  type="number"
                  step="0.01"
                  value={discount}
                  onChange={(ev) => setDiscount(Number(ev.target.value))}
                  className="h-7 w-24 text-right text-xs"
                />
                <select
                  value={discountType}
                  onChange={(ev) => setDiscountType(ev.target.value as 'fixed' | 'percentage')}
                  className="h-7 w-10 rounded-md border border-input bg-background text-xs"
                >
                  <option value="fixed">{currency?.symbol ?? '#'}</option>
                  <option value="percentage">%</option>
                </select>
              </div>
            </div>

            {taxPerItem === 'NO' && (
              <div className="py-1">
                <div className="mb-1 text-[10px] text-muted-foreground">Taxes</div>
                <TaxPicker taxTypes={taxTypes} selected={docTaxIds} onChange={setDocTaxIds} />
              </div>
            )}

            <TotalRow label="Tax" value={formatMoney(totals.totalTax, currency)} />
            <div className="mt-1 border-t border-border pt-1">
              <TotalRow label="Total" value={formatMoney(totals.total, currency)} strong />
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

const Labelled = ({ label, children }: { label: string; children: React.ReactNode }) => (
  <label className="flex flex-col gap-1 text-[10px] text-muted-foreground">
    {label}
    {children}
  </label>
);

const TotalRow = ({ label, value, strong }: { label: string; value: string; strong?: boolean }) => (
  <div className="flex items-baseline justify-between gap-3 py-1 text-xs">
    <span className="text-muted-foreground">{label}</span>
    <span className={`tabular-nums ${strong ? 'text-sm font-semibold' : ''}`}>{value}</span>
  </div>
);

/** Multi-select over the company's tax types, rendered as toggles because there are only ever a handful. */
const TaxPicker = ({
  taxTypes,
  selected,
  onChange,
}: {
  taxTypes: TaxType[];
  selected: number[];
  onChange: (ids: number[]) => void;
}) => {
  if (!taxTypes.length) return <span className="text-[10px] text-muted-foreground">No tax types defined.</span>;

  return (
    <div className="flex flex-wrap gap-1">
      {taxTypes.map((t) => {
        const on = selected.includes(t.id);
        return (
          <button
            key={t.id}
            type="button"
            onClick={() => onChange(on ? selected.filter((i) => i !== t.id) : [...selected, t.id])}
            className={`rounded-full px-2 py-0.5 text-[10px] ring-1 ring-inset transition-colors ${
              on ? 'bg-primary/10 text-primary ring-primary/30' : 'text-muted-foreground ring-border hover:bg-muted'
            }`}
          >
            {t.name} {Number(t.percent)}%
          </button>
        );
      })}
    </div>
  );
};

// ── arithmetic ───────────────────────────────────────────────────────────────────────────────────

type LineTotals = { subTotal: number; discountVal: number; total: number; tax: number };

function computeLine(line: Line, taxTypes: TaxType[], taxPerItem: 'YES' | 'NO'): LineTotals {
  const subTotal = Math.round(line.price * (Number(line.quantity) || 0));
  const abs = Math.abs(subTotal);
  const discountVal =
    line.discount_type === 'percentage'
      ? Math.round((abs * (Number(line.discount) || 0)) / 100)
      : Math.min(fromMajor(line.discount), abs);
  const total = subTotal - discountVal;

  const tax =
    taxPerItem === 'YES'
      ? line.tax_type_ids.reduce(
          (sum, tid) =>
            sum +
            percentOf(
              total,
              taxTypes.find((t) => t.id === tid),
            ),
          0,
        )
      : 0;

  return { subTotal, discountVal, total, tax };
}

const percentOf = (base: number, taxType?: TaxType): number =>
  taxType ? Math.round((base * (Number(taxType.percent) || 0)) / 100) : 0;

type TotalsInput = {
  lines: Line[];
  discount: number;
  discountType: 'fixed' | 'percentage';
  docTaxIds: number[];
  taxPerItem: 'YES' | 'NO';
  taxIncluded: boolean;
  taxTypes: TaxType[];
};

function computeTotals(input: TotalsInput) {
  const { lines, discount, discountType, docTaxIds, taxPerItem, taxIncluded, taxTypes } = input;

  const computed = lines.map((l) => computeLine(l, taxTypes, taxPerItem));
  const subTotal = computed.reduce((sum, c) => sum + c.total, 0);

  const documentDiscount =
    discountType === 'percentage'
      ? Math.round((Math.abs(subTotal) * (Number(discount) || 0)) / 100)
      : Math.min(fromMajor(discount), Math.abs(subTotal));

  const taxBase = subTotal - documentDiscount;

  let totalTax: number;
  if (taxPerItem === 'YES') {
    totalTax = computed.reduce((sum, c) => sum + c.tax, 0);
  } else {
    const chosen = docTaxIds.map((tid) => taxTypes.find((t) => t.id === tid)).filter((t): t is TaxType => Boolean(t));
    const simple = chosen.filter((t) => !t.compound_tax).reduce((sum, t) => sum + percentOf(taxBase, t), 0);
    // A compound tax is charged on the base plus the simple taxes already added — the order matters and
    // reversing it silently under-charges.
    const compound = chosen.filter((t) => t.compound_tax).reduce((sum, t) => sum + percentOf(taxBase + simple, t), 0);
    totalTax = simple + compound;
  }

  return {
    subTotal,
    documentDiscount,
    taxBase,
    totalTax,
    total: taxIncluded ? taxBase : taxBase + totalTax,
  };
}

/** The tax row shape upstream persists alongside a document or a line. */
function taxLine(taxTypeId: number, taxTypes: TaxType[], base: number): Partial<DocumentTax> & { tax_type_id: number } {
  const t = taxTypes.find((x) => x.id === taxTypeId);
  return {
    tax_type_id: taxTypeId,
    name: t?.name,
    percent: Number(t?.percent) || 0,
    compound_tax: t?.compound_tax ?? false,
    amount: percentOf(base, t),
  };
}
