import type { ReactNode } from 'react';
import type { Address, Customer, Expense, Invoice, Item, Payment } from './shared';
import { useEffect, useState } from 'react';
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import { formatMoney, fromMajor, toInputDate, toMajor } from './format';
import {
  useCurrency,
  useLookups,
  useNextNumber,
  useResourceDetail,
  useResourceList,
  useResourceMutations,
} from './useInvoiceShelfData';

// The four small record forms: customer, item, payment, expense. Dialogs rather than full screens —
// each is a handful of fields, and unlike a document none of them needs a line-item table.
//
// One shape for all four: `id === 'new'` creates, a number edits, and the form seeds itself once from the
// loaded record. The seed is guarded by a `loaded` flag rather than a dependency list because react-query
// re-delivers the record on every background refetch, and re-seeding mid-edit would silently discard what
// the user had typed.
//
// Money is entered in MAJOR units and written back in minor ones (`fromMajor`), the same convention as the
// document editor. Reading goes the other way through `toMajor`.

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

// ── shared bits ──────────────────────────────────────────────────────────────────────────────────

const Row = ({ children }: { children: ReactNode }) => <div className="grid gap-3 sm:grid-cols-2">{children}</div>;

const Cell = ({ label, children }: { label: string; children: ReactNode }) => (
  <div className="space-y-1">
    <Label className="text-[10px] text-muted-foreground">{label}</Label>
    {children}
  </div>
);

const Select = ({
  value,
  onChange,
  children,
}: {
  value: string | number;
  onChange: (value: string) => void;
  children: ReactNode;
}) => (
  <select
    value={value}
    onChange={(ev) => onChange(ev.target.value)}
    className="h-8 w-full rounded-md border border-input bg-background px-2 text-xs"
  >
    {children}
  </select>
);

const EditorShell = ({
  title,
  description,
  canSave,
  busy,
  onSave,
  onClose,
  children,
  wide,
}: {
  title: string;
  description?: string;
  canSave: boolean;
  busy: boolean;
  onSave: () => void;
  onClose: () => void;
  children: ReactNode;
  wide?: boolean;
}) => (
  <Dialog open onOpenChange={(open) => !open && onClose()}>
    <DialogContent className={wide ? 'sm:max-w-2xl' : 'sm:max-w-lg'}>
      <DialogHeader>
        <DialogTitle>{title}</DialogTitle>
        {description && <DialogDescription>{description}</DialogDescription>}
      </DialogHeader>

      <div className="max-h-[65vh] space-y-3 overflow-y-auto pr-1">{children}</div>

      <DialogFooter>
        <Button variant="ghost" size="sm" onClick={onClose} disabled={busy}>
          Cancel
        </Button>
        <Button size="sm" onClick={onSave} disabled={!canSave || busy}>
          {busy ? 'Saving…' : 'Save'}
        </Button>
      </DialogFooter>
    </DialogContent>
  </Dialog>
);

// ── customer ─────────────────────────────────────────────────────────────────────────────────────

const EMPTY_ADDRESS = {
  name: '',
  address_street_1: '',
  address_street_2: '',
  city: '',
  state: '',
  zip: '',
  phone: '',
  country_id: '' as number | '',
};

type AddressForm = typeof EMPTY_ADDRESS;

const fromAddress = (a: Address | null | undefined): AddressForm =>
  a
    ? {
        name: a.name ?? '',
        address_street_1: a.address_street_1 ?? '',
        address_street_2: a.address_street_2 ?? '',
        city: a.city ?? '',
        state: a.state ?? '',
        zip: a.zip ?? '',
        phone: a.phone ?? '',
        country_id: a.country_id ?? '',
      }
    : { ...EMPTY_ADDRESS };

export const CustomerEditor = ({ id, onClose }: EditorProps) => {
  const isNew = id === 'new';
  const { currencies } = useLookups();
  const currency = useCurrency();
  const { record } = useResourceDetail<Customer>('customers', isNew ? null : (id as number));
  const { create, update } = useResourceMutations('customers');
  // Read-only upstream; the address selects are unusable without it.
  const { rows: countries } = useResourceList<{ id: number; name: string }>('countries');

  const [form, setForm] = useState({
    name: '',
    email: '',
    phone: '',
    contact_name: '',
    company_name: '',
    website: '',
    prefix: '',
    tax_id: '',
    currency_id: '' as number | '',
    enable_portal: false,
  });
  const [billing, setBilling] = useState<AddressForm>({ ...EMPTY_ADDRESS });
  const [shipping, setShipping] = useState<AddressForm>({ ...EMPTY_ADDRESS });
  const [loaded, setLoaded] = useState(false);

  useEffect(() => {
    if (loaded || isNew || !record) return;
    setForm({
      name: record.name ?? '',
      email: record.email ?? '',
      phone: record.phone ?? '',
      contact_name: record.contact_name ?? '',
      company_name: record.company_name ?? '',
      website: record.website ?? '',
      prefix: record.prefix ?? '',
      tax_id: record.tax_id ?? '',
      currency_id: record.currency_id ?? '',
      enable_portal: Boolean(record.enable_portal),
    });
    setBilling(fromAddress(record.billing));
    setShipping(fromAddress(record.shipping));
    setLoaded(true);
  }, [record, isNew, loaded]);

  const patch = (changes: Partial<typeof form>) => setForm((prev) => ({ ...prev, ...changes }));

  const save = () => {
    const payload = {
      ...form,
      email: form.email || null,
      currency_id: form.currency_id === '' ? (currency?.id ?? null) : form.currency_id,
      billing: { ...billing, country_id: billing.country_id === '' ? null : billing.country_id },
      shipping: { ...shipping, country_id: shipping.country_id === '' ? null : shipping.country_id },
    };
    if (isNew) create.mutate(payload, { onSuccess: onClose });
    else update.mutate({ id: id as number, payload }, { onSuccess: onClose });
  };

  return (
    <EditorShell
      title={isNew ? 'New customer' : `Edit ${form.name || 'customer'}`}
      canSave={form.name.trim() !== ''}
      busy={create.isPending || update.isPending}
      onSave={save}
      onClose={onClose}
      wide
    >
      <Row>
        <Cell label="Display name">
          <Input value={form.name} onChange={(ev) => patch({ name: ev.target.value })} className="h-8 text-xs" />
        </Cell>
        <Cell label="Contact name">
          <Input
            value={form.contact_name}
            onChange={(ev) => patch({ contact_name: ev.target.value })}
            className="h-8 text-xs"
          />
        </Cell>
        <Cell label="Email">
          <Input
            type="email"
            value={form.email}
            onChange={(ev) => patch({ email: ev.target.value })}
            className="h-8 text-xs"
          />
        </Cell>
        <Cell label="Phone">
          <Input value={form.phone} onChange={(ev) => patch({ phone: ev.target.value })} className="h-8 text-xs" />
        </Cell>
        <Cell label="Company">
          <Input
            value={form.company_name}
            onChange={(ev) => patch({ company_name: ev.target.value })}
            className="h-8 text-xs"
          />
        </Cell>
        <Cell label="Website">
          <Input value={form.website} onChange={(ev) => patch({ website: ev.target.value })} className="h-8 text-xs" />
        </Cell>
        <Cell label="Tax ID">
          <Input value={form.tax_id} onChange={(ev) => patch({ tax_id: ev.target.value })} className="h-8 text-xs" />
        </Cell>
        <Cell label="Currency">
          <Select value={form.currency_id} onChange={(v) => patch({ currency_id: v ? Number(v) : '' })}>
            <option value="">Company default</option>
            {currencies.map((c) => (
              <option key={c.id} value={c.id}>
                {c.code} — {c.name}
              </option>
            ))}
          </Select>
        </Cell>
      </Row>

      <label className="flex items-center gap-2 text-xs">
        <Checkbox
          checked={form.enable_portal}
          onCheckedChange={(checked) => patch({ enable_portal: checked === true })}
        />
        Give this customer a portal login
      </label>

      <AddressFields label="Billing address" value={billing} onChange={setBilling} countries={countries} />
      <AddressFields
        label="Shipping address"
        value={shipping}
        onChange={setShipping}
        countries={countries}
        onCopy={() => setShipping({ ...billing })}
      />
    </EditorShell>
  );
};

const AddressFields = ({
  label,
  value,
  onChange,
  countries,
  onCopy,
}: {
  label: string;
  value: AddressForm;
  onChange: (next: AddressForm) => void;
  countries: { id: number; name: string }[];
  onCopy?: () => void;
}) => {
  const patch = (changes: Partial<AddressForm>) => onChange({ ...value, ...changes });

  return (
    <div className="rounded-lg border border-border p-2">
      <div className="mb-2 flex items-center justify-between">
        <div className="text-[10px] font-medium uppercase tracking-wide text-muted-foreground">{label}</div>
        {onCopy && (
          <Button variant="ghost" size="sm" className="h-6 text-[10px]" onClick={onCopy}>
            Same as billing
          </Button>
        )}
      </div>
      <div className="grid gap-2 sm:grid-cols-2">
        <Input
          placeholder="Name"
          value={value.name}
          onChange={(ev) => patch({ name: ev.target.value })}
          className="h-8 text-xs"
        />
        <Input
          placeholder="Phone"
          value={value.phone}
          onChange={(ev) => patch({ phone: ev.target.value })}
          className="h-8 text-xs"
        />
        <Input
          placeholder="Street"
          value={value.address_street_1}
          onChange={(ev) => patch({ address_street_1: ev.target.value })}
          className="h-8 text-xs"
        />
        <Input
          placeholder="Street 2"
          value={value.address_street_2}
          onChange={(ev) => patch({ address_street_2: ev.target.value })}
          className="h-8 text-xs"
        />
        <Input
          placeholder="City"
          value={value.city}
          onChange={(ev) => patch({ city: ev.target.value })}
          className="h-8 text-xs"
        />
        <Input
          placeholder="State"
          value={value.state}
          onChange={(ev) => patch({ state: ev.target.value })}
          className="h-8 text-xs"
        />
        <Input
          placeholder="ZIP"
          value={value.zip}
          onChange={(ev) => patch({ zip: ev.target.value })}
          className="h-8 text-xs"
        />
        <Select value={value.country_id} onChange={(v) => patch({ country_id: v ? Number(v) : '' })}>
          <option value="">Country…</option>
          {countries.map((c) => (
            <option key={c.id} value={c.id}>
              {c.name}
            </option>
          ))}
        </Select>
      </div>
    </div>
  );
};

// ── item ─────────────────────────────────────────────────────────────────────────────────────────

export const ItemEditor = ({ id, onClose }: EditorProps) => {
  const isNew = id === 'new';
  const currency = useCurrency();
  const { units } = useLookups();
  const { record } = useResourceDetail<Item>('items', isNew ? null : (id as number));
  const { create, update } = useResourceMutations('items');

  const [name, setName] = useState('');
  const [description, setDescription] = useState('');
  const [price, setPrice] = useState(0);
  const [unitId, setUnitId] = useState<number | ''>('');
  const [loaded, setLoaded] = useState(false);

  useEffect(() => {
    if (loaded || isNew || !record) return;
    setName(record.name ?? '');
    setDescription(record.description ?? '');
    setPrice(toMajor(record.price));
    setUnitId(record.unit_id ?? '');
    setLoaded(true);
  }, [record, isNew, loaded]);

  const save = () => {
    const payload = {
      name: name.trim(),
      description: description || null,
      price: fromMajor(price),
      unit_id: unitId === '' ? null : unitId,
      currency_id: record?.currency_id ?? currency?.id ?? null,
    };
    if (isNew) create.mutate(payload, { onSuccess: onClose });
    else update.mutate({ id: id as number, payload }, { onSuccess: onClose });
  };

  return (
    <EditorShell
      title={isNew ? 'New item' : `Edit ${name || 'item'}`}
      description="A saved line you can drop onto any invoice or estimate."
      canSave={name.trim() !== ''}
      busy={create.isPending || update.isPending}
      onSave={save}
      onClose={onClose}
    >
      <Cell label="Name">
        <Input value={name} onChange={(ev) => setName(ev.target.value)} className="h-8 text-xs" />
      </Cell>
      <Row>
        <Cell label={`Price (${currency?.code ?? ''})`}>
          <Input
            type="number"
            step="0.01"
            value={price}
            onChange={(ev) => setPrice(Number(ev.target.value))}
            className="h-8 text-xs"
          />
        </Cell>
        <Cell label="Unit">
          <Select value={unitId} onChange={(v) => setUnitId(v ? Number(v) : '')}>
            <option value="">None</option>
            {units.map((u) => (
              <option key={u.id} value={u.id}>
                {u.name}
              </option>
            ))}
          </Select>
        </Cell>
      </Row>
      <Cell label="Description">
        <Textarea value={description} onChange={(ev) => setDescription(ev.target.value)} rows={3} className="text-xs" />
      </Cell>
    </EditorShell>
  );
};

// ── payment ──────────────────────────────────────────────────────────────────────────────────────

/**
 * `forInvoiceId` is the "Record payment" path from the invoices list: the invoice is known, its customer
 * and outstanding amount are not, so they are fetched and filled in rather than made the user's problem.
 */
export const PaymentEditor = ({ id, forInvoiceId, onClose }: EditorProps & { forInvoiceId?: number }) => {
  const isNew = id === 'new';
  const currency = useCurrency();
  const { customers, paymentMethods } = useLookups();
  const { record } = useResourceDetail<Payment>('payments', isNew ? null : (id as number));
  const { record: seedInvoice } = useResourceDetail<Invoice>('invoices', isNew ? (forInvoiceId ?? null) : null);
  const { create, update } = useResourceMutations('payments');
  const nextNumber = useNextNumber('payment', isNew);

  const [customerId, setCustomerId] = useState<number | ''>('');
  const [invoiceId, setInvoiceId] = useState<number | ''>('');
  const [number, setNumber] = useState('');
  const [date, setDate] = useState(() => toInputDate(new Date().toISOString()));
  const [amount, setAmount] = useState(0);
  const [methodId, setMethodId] = useState<number | ''>('');
  const [notes, setNotes] = useState('');
  const [loaded, setLoaded] = useState(false);

  // `status: 'DUE'` is upstream's own token for "paid_status in (UNPAID, PARTIALLY_PAID)" — the only
  // invoices a payment can sensibly be attached to. Fetched only once a customer is chosen.
  const { rows: openInvoices } = useResourceList<Invoice>(
    'invoices',
    { customer_id: customerId || undefined, status: 'DUE', limit: 100 },
    customerId !== '',
  );

  useEffect(() => {
    if (loaded) return;
    if (isNew) {
      if (!nextNumber) return;
      // Wait for the seed invoice too, or the form would open blank and then jump under the user's cursor.
      if (forInvoiceId != null && !seedInvoice) return;
      setNumber(nextNumber);
      if (seedInvoice) {
        setCustomerId(seedInvoice.customer_id);
        setInvoiceId(seedInvoice.id);
        setAmount(toMajor(seedInvoice.due_amount));
      }
      setLoaded(true);
      return;
    }
    if (!record) return;
    setCustomerId(record.customer_id ?? '');
    setInvoiceId(record.invoice_id ?? '');
    setNumber(record.payment_number ?? '');
    setDate(toInputDate(record.payment_date));
    setAmount(toMajor(record.amount));
    setMethodId(record.payment_method_id ?? '');
    setNotes(record.notes ?? '');
    setLoaded(true);
  }, [record, isNew, nextNumber, seedInvoice, forInvoiceId, loaded]);

  // The pre-selected invoice may not be in the customer's open list yet (that query runs a beat later), so
  // fall back to the seed for the over-payment warning.
  const selected =
    openInvoices.find((inv) => inv.id === invoiceId) ??
    (seedInvoice && seedInvoice.id === invoiceId ? seedInvoice : undefined);

  const save = () => {
    const payload = {
      payment_date: date,
      payment_number: number.trim(),
      customer_id: customerId === '' ? null : customerId,
      invoice_id: invoiceId === '' ? null : invoiceId,
      payment_method_id: methodId === '' ? null : methodId,
      amount: fromMajor(amount),
      notes: notes || null,
      currency_id: record?.currency_id ?? currency?.id ?? null,
      exchange_rate: 1,
    };
    if (isNew) create.mutate(payload, { onSuccess: onClose });
    else update.mutate({ id: id as number, payload }, { onSuccess: onClose });
  };

  return (
    <EditorShell
      title={isNew ? 'Record a payment' : `Edit payment ${number}`}
      description="Recording a payment reduces the linked invoice's due amount. It does not email anyone."
      canSave={customerId !== '' && number.trim() !== '' && date !== '' && amount > 0}
      busy={create.isPending || update.isPending}
      onSave={save}
      onClose={onClose}
    >
      <Row>
        <Cell label="Date">
          <Input type="date" value={date} onChange={(ev) => setDate(ev.target.value)} className="h-8 text-xs" />
        </Cell>
        <Cell label="Payment number">
          <Input value={number} onChange={(ev) => setNumber(ev.target.value)} className="h-8 text-xs" />
        </Cell>
        <Cell label="Customer">
          <Select
            value={customerId}
            onChange={(v) => {
              setCustomerId(v ? Number(v) : '');
              // The old invoice belongs to the old customer; keeping it would post the payment to a
              // document the new customer does not own.
              setInvoiceId('');
            }}
          >
            <option value="">Select…</option>
            {customers.map((c) => (
              <option key={c.id} value={c.id}>
                {c.name}
              </option>
            ))}
          </Select>
        </Cell>
        <Cell label="Invoice">
          <Select
            value={invoiceId}
            onChange={(v) => {
              const next = v ? Number(v) : '';
              setInvoiceId(next);
              // Default the amount to whatever is still outstanding — the overwhelmingly common case, and
              // still editable for a part payment.
              const inv = openInvoices.find((i) => i.id === next);
              if (inv) setAmount(toMajor(inv.due_amount));
            }}
          >
            <option value="">{customerId === '' ? 'Pick a customer first' : 'Unlinked'}</option>
            {/* The pre-selected invoice is listed explicitly in case the open-invoice query has not landed
                yet — otherwise the select would show a blank for a value it does hold. */}
            {seedInvoice && !openInvoices.some((inv) => inv.id === seedInvoice.id) && (
              <option value={seedInvoice.id}>
                {seedInvoice.invoice_number} — {formatMoney(seedInvoice.due_amount, currency)} due
              </option>
            )}
            {openInvoices.map((inv) => (
              <option key={inv.id} value={inv.id}>
                {inv.invoice_number} — {formatMoney(inv.due_amount, currency)} due
              </option>
            ))}
          </Select>
        </Cell>
        <Cell label={`Amount (${currency?.code ?? ''})`}>
          <Input
            type="number"
            step="0.01"
            value={amount}
            onChange={(ev) => setAmount(Number(ev.target.value))}
            className="h-8 text-xs"
          />
        </Cell>
        <Cell label="Method">
          <Select value={methodId} onChange={(v) => setMethodId(v ? Number(v) : '')}>
            <option value="">None</option>
            {paymentMethods.map((m) => (
              <option key={m.id} value={m.id}>
                {m.name}
              </option>
            ))}
          </Select>
        </Cell>
      </Row>

      {selected && fromMajor(amount) > Number(selected.due_amount ?? 0) && (
        <p className="text-[10px] text-amber-600">
          More than the {formatMoney(selected.due_amount, currency)} outstanding on {selected.invoice_number}.
          InvoiceShelf will refuse this.
        </p>
      )}

      <Cell label="Notes">
        <Textarea value={notes} onChange={(ev) => setNotes(ev.target.value)} rows={3} className="text-xs" />
      </Cell>
    </EditorShell>
  );
};

// ── expense ──────────────────────────────────────────────────────────────────────────────────────

export const ExpenseEditor = ({ id, onClose }: EditorProps) => {
  const isNew = id === 'new';
  const currency = useCurrency();
  const { customers, categories, paymentMethods } = useLookups();
  const { record } = useResourceDetail<Expense>('expenses', isNew ? null : (id as number));
  const { create, update } = useResourceMutations('expenses');

  const [date, setDate] = useState(() => toInputDate(new Date().toISOString()));
  const [number, setNumber] = useState('');
  const [categoryId, setCategoryId] = useState<number | ''>('');
  const [customerId, setCustomerId] = useState<number | ''>('');
  const [methodId, setMethodId] = useState<number | ''>('');
  const [amount, setAmount] = useState(0);
  const [notes, setNotes] = useState('');
  const [loaded, setLoaded] = useState(false);

  useEffect(() => {
    if (loaded || isNew || !record) return;
    setDate(toInputDate(record.expense_date));
    setNumber(record.expense_number ?? '');
    setCategoryId(record.expense_category_id ?? '');
    setCustomerId(record.customer_id ?? '');
    setMethodId(record.payment_method_id ?? '');
    setAmount(toMajor(record.amount));
    setNotes(record.notes ?? '');
    setLoaded(true);
  }, [record, isNew, loaded]);

  const save = () => {
    const payload = {
      expense_date: date,
      expense_number: number || null,
      expense_category_id: categoryId === '' ? null : categoryId,
      customer_id: customerId === '' ? null : customerId,
      payment_method_id: methodId === '' ? null : methodId,
      amount: fromMajor(amount),
      notes: notes || null,
      currency_id: record?.currency_id ?? currency?.id ?? null,
      exchange_rate: 1,
    };
    if (isNew) create.mutate(payload, { onSuccess: onClose });
    else update.mutate({ id: id as number, payload }, { onSuccess: onClose });
  };

  return (
    <EditorShell
      title={isNew ? 'New expense' : 'Edit expense'}
      canSave={categoryId !== '' && date !== '' && amount !== 0}
      busy={create.isPending || update.isPending}
      onSave={save}
      onClose={onClose}
    >
      <Row>
        <Cell label="Date">
          <Input type="date" value={date} onChange={(ev) => setDate(ev.target.value)} className="h-8 text-xs" />
        </Cell>
        <Cell label="Number">
          <Input
            value={number}
            onChange={(ev) => setNumber(ev.target.value)}
            placeholder="Optional"
            className="h-8 text-xs"
          />
        </Cell>
        <Cell label="Category">
          <Select value={categoryId} onChange={(v) => setCategoryId(v ? Number(v) : '')}>
            <option value="">Select…</option>
            {categories.map((c) => (
              <option key={c.id} value={c.id}>
                {c.name}
              </option>
            ))}
          </Select>
        </Cell>
        <Cell label={`Amount (${currency?.code ?? ''})`}>
          <Input
            type="number"
            step="0.01"
            value={amount}
            onChange={(ev) => setAmount(Number(ev.target.value))}
            className="h-8 text-xs"
          />
        </Cell>
        <Cell label="Customer">
          <Select value={customerId} onChange={(v) => setCustomerId(v ? Number(v) : '')}>
            <option value="">None</option>
            {customers.map((c) => (
              <option key={c.id} value={c.id}>
                {c.name}
              </option>
            ))}
          </Select>
        </Cell>
        <Cell label="Paid with">
          <Select value={methodId} onChange={(v) => setMethodId(v ? Number(v) : '')}>
            <option value="">None</option>
            {paymentMethods.map((m) => (
              <option key={m.id} value={m.id}>
                {m.name}
              </option>
            ))}
          </Select>
        </Cell>
      </Row>

      <Cell label="Notes">
        <Textarea value={notes} onChange={(ev) => setNotes(ev.target.value)} rows={3} className="text-xs" />
      </Cell>

      {/* Receipts are read-only here. Attaching one is a multipart upload to a separate endpoint that the
          sidecar's JSON allow-list does not carry; an existing receipt is linked from the expenses list. */}
      {record?.attachment_receipt_url && (
        <p className="text-[10px] text-muted-foreground">
          This expense has a receipt attached.{' '}
          <a
            href={record.attachment_receipt_url}
            target="_blank"
            rel="noopener noreferrer"
            className="underline hover:text-foreground"
          >
            Open it
          </a>
          . Replacing it has to be done in InvoiceShelf.
        </p>
      )}
    </EditorShell>
  );
};
