import { useEffect, useMemo, useState, type ReactNode } from 'react'
import { useNavigate } from 'react-router-dom'
import { formatINR, fromRupees } from '@sims/domain'
import { Badge, Button, Field, Notice, PageHeader, Toolbar } from '@sims/ui'
import {
  createDocument, getBillingSettings, getClients, getModules, getPrices, previewDocument,
  KIND_LABEL, type Client, type DocTotals, type Kind, type ModulePrice,
} from '../api'
import { LivePreview } from '../components/LivePreview'
import { useData } from './Clients'

const today = () => new Date().toISOString().slice(0, 10)

interface LineDraft {
  moduleId: string; kind: Kind | ''; qty: string; unitRs: string; description: string
  edition: string; contentText: string
}
const EMPTY_LINE: LineDraft = {
  moduleId: '', kind: '', qty: '1', unitRs: '', description: '', edition: 'standard', contentText: '',
}

const editionsOf = (prices: ModulePrice[]): string[] => [...new Set(prices.map((p) => p.edition))]
const splitContent = (s: string): string[] => s.split('\n').map((x) => x.trim()).filter((x) => x !== '')

function latestPrice(prices: ModulePrice[], kind: Kind, edition = 'standard'): number | null {
  const hit = prices
    .filter((p) => p.kind === kind && p.edition === edition && p.effectiveFrom <= today())
    .sort((a, b) => (a.effectiveFrom < b.effectiveFrom ? 1 : -1))[0]
  return hit !== undefined ? hit.pricePaise : null
}
function editionPrice(prices: ModulePrice[], edition: string, kind: Kind | ''): number | null {
  if (kind !== '') return latestPrice(prices, kind, edition)
  const hit = prices
    .filter((p) => p.edition === edition && p.effectiveFrom <= today())
    .sort((a, b) => (a.effectiveFrom < b.effectiveFrom ? 1 : -1))[0]
  return hit !== undefined ? hit.pricePaise : null
}

/** matchMedia hook — the composer collapses to one column on the owner's phone. */
function useNarrow(): boolean {
  const [narrow, setNarrow] = useState(() => typeof window !== 'undefined' && window.matchMedia('(max-width: 800px)').matches)
  useEffect(() => {
    const mq = window.matchMedia('(max-width: 800px)')
    const on = () => setNarrow(mq.matches)
    mq.addEventListener('change', on)
    return () => mq.removeEventListener('change', on)
  }, [])
  return narrow
}

/** Value that flashes a highlight for 250ms when it changes (cause→effect visible). */
function Pulse({ children, on }: { children: ReactNode; on: string | number }) {
  const [pulse, setPulse] = useState(false)
  useEffect(() => { setPulse(true); const t = setTimeout(() => setPulse(false), 250); return () => clearTimeout(t) }, [on])
  return <span style={{ transition: 'background 250ms ease', background: pulse ? 'var(--accent-soft, #dce8ff)' : 'transparent', borderRadius: 4, padding: '0 3px' }}>{children}</span>
}

/** The quotation-in-minutes composer: client → lines → live preview → Save Draft. */
export function NewDocument() {
  const nav = useNavigate()
  const narrow = useNarrow()
  const modules = useData(getModules, [])
  const [docType, setDocType] = useState<'QUOTATION' | 'PROFORMA' | 'INVOICE'>('QUOTATION')
  const [lines, setLines] = useState<LineDraft[]>([{ ...EMPTY_LINE }])
  const [terms, setTerms] = useState('')
  const [error, setError] = useState<string | undefined>()
  const [saving, setSaving] = useState(false)

  // D18: invoice due date — auto-filled from billing.payment_terms_days, editable.
  const [dueDate, setDueDate] = useState('')
  const [termsDays, setTermsDays] = useState<number | undefined>()
  useEffect(() => {
    getBillingSettings().then((s) => setTermsDays(s.paymentTermsDays)).catch(() => { /* field stays manual */ })
  }, [])
  useEffect(() => {
    if (docType === 'INVOICE' && dueDate === '' && termsDays !== undefined) {
      const d = new Date(); d.setUTCDate(d.getUTCDate() + termsDays)
      setDueDate(d.toISOString().slice(0, 10))
    }
  }, [docType, termsDays]) // eslint-disable-line react-hooks/exhaustive-deps -- prefill once, never clobber edits

  // -- live preview wiring: server totals + warnings replace all client money math --
  const [commitNonce, setCommitNonce] = useState(0)
  const commit = () => setCommitNonce((n) => n + 1)
  const [totals, setTotals] = useState<DocTotals | undefined>()
  const [warnings, setWarnings] = useState<string[]>([])
  const [previewBusy, setPreviewBusy] = useState(false)
  const [previewErr, setPreviewErr] = useState<string | null>(null)
  const [sheetOpen, setSheetOpen] = useState(false) // narrow: full-screen paper sheet

  // -- client type-ahead (debounced) --
  const [q, setQ] = useState('')
  const [hits, setHits] = useState<Client[]>([])
  const [client, setClient] = useState<Client | undefined>()
  useEffect(() => {
    if (client !== undefined || q.trim() === '') { setHits([]); return }
    const t = setTimeout(() => { getClients(q).then(setHits).catch(() => setHits([])) }, 250)
    return () => clearTimeout(t)
  }, [q, client])

  const [pricesByModule, setPricesByModule] = useState<Record<string, ModulePrice[]>>({})
  const loadPrices = (moduleId: string): Promise<ModulePrice[]> => {
    const cached = pricesByModule[moduleId]
    if (cached !== undefined) return Promise.resolve(cached)
    return getPrices(moduleId).then((prices) => { setPricesByModule((prev) => ({ ...prev, [moduleId]: prices })); return prices })
  }
  const prefill = (index: number, moduleId: string, kind: Kind, edition: string) => {
    loadPrices(moduleId).then((prices) => {
      const paise = latestPrice(prices, kind, edition)
      setLines((prev) => prev.map((l, i) => (i === index ? { ...l, unitRs: paise !== null ? String(paise / 100) : l.unitRs } : l)))
    }).catch(() => { /* no prefill — the unit price stays editable */ })
  }
  const setLine = (index: number, patch: Partial<LineDraft>) =>
    setLines((prev) => prev.map((l, i) => (i === index ? { ...l, ...patch } : l)))

  /** The ONE body builder — save() and the live preview post byte-identical bodies. */
  const buildDraftBody = () => ({
    docType,
    clientId: client?.id ?? '',
    lines: lines.filter((l) => l.moduleId !== '' && l.kind !== '').map((l) => ({
      moduleId: l.moduleId, kind: l.kind as Kind, qty: Number(l.qty),
      ...(l.edition !== 'standard' ? { edition: l.edition } : {}),
      ...(l.unitRs !== '' ? { unitPricePaise: fromRupees(Number(l.unitRs)) } : {}),
      ...(l.description !== '' ? { description: l.description } : {}),
      contentLines: splitContent(l.contentText),
    })),
    ...(terms.trim() !== '' ? { terms: terms.trim() } : {}),
    ...(docType === 'INVOICE' && dueDate !== '' ? { dueDate } : {}),
  })
  const previewBody = useMemo(() => JSON.stringify(buildDraftBody()), [docType, client, lines, terms, dueDate])

  const save = () => {
    if (client === undefined) { setError('Pick a client first'); return }
    const ready = lines.filter((l) => l.moduleId !== '' && l.kind !== '')
    if (ready.length === 0) { setError('Add at least one line (module + kind)'); return }
    for (const l of ready) {
      if (!Number.isFinite(Number(l.qty)) || Number(l.qty) <= 0) { setError('Every line needs a positive quantity'); return }
      if (l.unitRs !== '' && !Number.isFinite(Number(l.unitRs))) { setError('Unit price must be a rupee amount'); return }
    }
    setError(undefined); setSaving(true)
    createDocument(buildDraftBody())
      .then((d) => nav(`/documents/${d.id}`))
      .catch((e: Error) => { setError(e.message); setSaving(false) })
  }

  const pendingClient = client === undefined
  const stripDim = previewBusy || previewErr !== null

  const totalsStrip = (
    <div style={{ display: 'flex', gap: 14, flexWrap: 'wrap', alignItems: 'baseline', padding: '8px 12px', borderTop: '1px solid var(--border)', background: 'var(--bg-raised)', opacity: stripDim ? 0.6 : 1, transition: 'opacity 120ms ease' }}>
      <Cell label="Taxable" value={totals ? formatINR(totals.taxablePaise) : '—'} />
      {totals && totals.igstPaise > 0
        ? <Cell label="IGST" value={formatINR(totals.igstPaise)} />
        : <><Cell label="CGST" value={totals ? formatINR(totals.cgstPaise) : '—'} /><Cell label="SGST" value={totals ? formatINR(totals.sgstPaise) : '—'} /></>}
      {totals && totals.roundOffPaise !== 0 && <Cell label="Round-off" value={formatINR(totals.roundOffPaise)} />}
      <Cell label="Payable" value={totals ? formatINR(totals.payablePaise) : '—'} strong />
      {pendingClient && <Badge tone="warn">split pending client</Badge>}
      {warnings.length > 0 && <Badge tone="warn">{warnings.length} price gap(s)</Badge>}
    </div>
  )

  const paper = (
    <LivePreview
      body={previewBody}
      commitNonce={commitNonce}
      fetchPreview={(b, signal) => previewDocument(JSON.parse(b), signal)}
      onResult={(r) => { setTotals(r.totals); setWarnings(r.warnings ?? []) }}
      onStatus={(s) => { setPreviewBusy(s.busy); setPreviewErr(s.error) }}
    />
  )

  const form = (
    <div className="wf-page" style={{ overflow: 'auto', height: narrow ? 'auto' : 'calc(100vh - 200px)', paddingBottom: narrow ? 72 : 0 }}>
      <PageHeader title="New Document" desc="Compose a quotation, proforma or invoice. GST is computed and previewed live by the server." />

      <Field label="Client">
        {client !== undefined ? (
          <Toolbar>
            <Badge tone="accent">{client.code} · {client.name}</Badge>
            <Button onClick={() => { setClient(undefined); setQ(''); commit() }}>Change</Button>
          </Toolbar>
        ) : (
          <div style={{ position: 'relative', maxWidth: 340 }}>
            <input className="wf" placeholder="Type to search clients…" value={q} autoFocus onChange={(e) => setQ(e.target.value)} />
            {hits.length > 0 && (
              <div style={{ position: 'absolute', zIndex: 5, top: '100%', left: 0, right: 0, background: 'var(--bg-raised)', border: '1px solid var(--border)', borderRadius: 'var(--radius)', boxShadow: 'var(--shadow-lg)' }}>
                {hits.slice(0, 8).map((c) => (
                  <button key={c.id} type="button" className="wf" style={{ display: 'block', width: '100%', textAlign: 'left', border: 'none' }}
                    onClick={() => { setClient(c); setHits([]); commit() }}>
                    {c.code} · {c.name}
                  </button>
                ))}
              </div>
            )}
          </div>
        )}
      </Field>

      <Field label="Document type">
        <Toolbar>
          {(['QUOTATION', 'PROFORMA', 'INVOICE'] as const).map((t) => (
            <label key={t} style={{ display: 'flex', gap: 4, alignItems: 'center', fontSize: 13 }}>
              <input type="radio" name="doctype" checked={docType === t} onChange={() => { setDocType(t); commit() }} />
              {t === 'QUOTATION' ? 'Quotation' : t === 'PROFORMA' ? 'Proforma' : 'Invoice'}
            </label>
          ))}
        </Toolbar>
      </Field>
      {docType === 'INVOICE' && (
        <Field label={`Due date${termsDays !== undefined ? ` (auto: +${termsDays}d)` : ''}`}>
          <input
            className="wf" type="date" value={dueDate}
            onChange={(e) => { setDueDate(e.target.value); commit() }}
          />
        </Field>
      )}

      <h3>Lines</h3>
      {lines.map((l, i) => {
        const mod = modules.data?.find((m) => m.id === l.moduleId)
        const prices = pricesByModule[l.moduleId] ?? []
        const editions = editionsOf(prices)
        const priceGap = warnings.some((w) => mod !== undefined && w.includes(mod.code))
        return (
          <div key={i} className="wf-card" style={{ display: 'flex', gap: 10, flexWrap: 'wrap', alignItems: 'flex-end' }}>
            <Field label="Module">
              <select className="wf" value={l.moduleId}
                onChange={(e) => {
                  const moduleId = e.target.value
                  const picked = modules.data?.find((m) => m.id === moduleId)
                  setLine(i, { moduleId, kind: '', unitRs: '', edition: 'standard', contentText: (picked?.quoteContent ?? []).join('\n') })
                  if (moduleId !== '') {
                    loadPrices(moduleId).then((ps) => {
                      const eds = editionsOf(ps); const def = eds.includes('standard') ? 'standard' : (eds[0] ?? 'standard')
                      if (def !== 'standard') setLine(i, { edition: def })
                    }).catch(() => { /* pricing stays manual */ })
                  }
                  commit()
                }}>
                <option value="">Module…</option>
                {(modules.data ?? []).filter((m) => m.active).map((m) => <option key={m.id} value={m.id}>{m.name}</option>)}
              </select>
            </Field>
            <Field label="Kind">
              <select className="wf" value={l.kind}
                onChange={(e) => { const kind = e.target.value as Kind; setLine(i, { kind }); if (l.moduleId !== '') prefill(i, l.moduleId, kind, l.edition); commit() }}>
                <option value="">Kind…</option>
                {(mod?.allowedKinds ?? []).map((k) => <option key={k} value={k}>{KIND_LABEL[k]}</option>)}
              </select>
            </Field>
            {editions.length > 1 && (
              <Field label="Pack">
                <select className="wf" value={l.edition}
                  onChange={(e) => { const edition = e.target.value; setLine(i, { edition }); if (l.moduleId !== '' && l.kind !== '') prefill(i, l.moduleId, l.kind, edition); commit() }}>
                  {editions.map((ed) => { const p = editionPrice(prices, ed, l.kind); return <option key={ed} value={ed}>{ed}{p !== null ? ` — ${formatINR(p)}` : ''}</option> })}
                </select>
              </Field>
            )}
            <Field label="Qty">
              <input className="wf num" style={{ width: 70 }} value={l.qty} onChange={(e) => setLine(i, { qty: e.target.value })} onBlur={commit} />
            </Field>
            <Field label="Unit ₹ (ex-GST)">
              <input className="wf num" style={{ width: 120, ...(priceGap ? { borderColor: 'var(--warn, #d08700)' } : {}) }}
                placeholder={priceGap ? 'No price — enter Unit ₹' : ''} value={l.unitRs}
                onChange={(e) => setLine(i, { unitRs: e.target.value })} onBlur={commit} />
            </Field>
            <Field label="Description (optional)">
              <input className="wf" value={l.description} onChange={(e) => setLine(i, { description: e.target.value })} onBlur={commit} />
            </Field>
            <Field label="What's included (one per line)">
              <textarea className="wf" rows={2} style={{ minWidth: 220 }} value={l.contentText} onChange={(e) => setLine(i, { contentText: e.target.value })} onBlur={commit} />
            </Field>
            {lines.length > 1 && (
              <Button tone="danger" onClick={() => { setLines((prev) => prev.filter((_x, j) => j !== i)); commit() }}>Remove</Button>
            )}
          </div>
        )
      })}
      <Toolbar>
        <Button onClick={() => { setLines((prev) => [...prev, { ...EMPTY_LINE }]); commit() }}>Add line</Button>
      </Toolbar>

      <Field label="Terms (optional)">
        <textarea className="wf" rows={2} style={{ maxWidth: 560 }} value={terms} onChange={(e) => setTerms(e.target.value)} onBlur={commit} />
      </Field>

      {modules.error !== undefined && <Notice tone="err">{modules.error}</Notice>}
      {error !== undefined && <Notice tone="err">{error}</Notice>}
      <div style={{ marginTop: 10 }}>
        <Button tone="primary" onClick={save}>{saving ? 'Saving…' : 'Save Draft'}</Button>
      </div>
    </div>
  )

  if (narrow) {
    return (
      <>
        {form}
        {/* pinned bar never disappears — always shows Payable + GST + Preview */}
        <div style={{ position: 'fixed', left: 0, right: 0, bottom: 0, zIndex: 20, display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10, padding: '8px 12px', background: 'var(--bg-raised)', borderTop: '1px solid var(--border)' }}>
          <div style={{ display: 'flex', gap: 12, opacity: stripDim ? 0.6 : 1 }}>
            <Cell label="GST" value={totals ? formatINR((totals.cgstPaise + totals.sgstPaise) || totals.igstPaise) : '—'} />
            <Cell label="Payable" value={totals ? formatINR(totals.payablePaise) : '—'} strong />
          </div>
          <Button tone="primary" onClick={() => setSheetOpen(true)}>Preview</Button>
        </div>
        {sheetOpen && (
          <div style={{ position: 'fixed', inset: 0, zIndex: 30, background: 'var(--bg-inset)', display: 'flex', flexDirection: 'column' }}>
            <div style={{ display: 'flex', justifyContent: 'flex-end', padding: 8 }}><Button onClick={() => setSheetOpen(false)}>Close</Button></div>
            <div style={{ flex: 1, margin: 12, marginTop: 0, background: '#fff', borderRadius: 4, overflow: 'hidden' }}>{paper}</div>
            {totalsStrip}
          </div>
        )}
      </>
    )
  }

  return (
    <div style={{ display: 'flex', gap: 16, alignItems: 'stretch' }}>
      <div style={{ flex: '0 0 600px', minWidth: 0 }}>{form}</div>
      <div style={{ flex: 1, minWidth: 0, position: 'sticky', top: 12, height: 'calc(100vh - 140px)', display: 'flex', flexDirection: 'column', background: 'var(--bg-inset)', borderRadius: 8, padding: 12 }}>
        <div style={{ flex: 1, minHeight: 0, background: '#fff', borderRadius: 4, boxShadow: '0 1px 6px rgba(0,0,0,.15)', overflow: 'hidden' }}>{paper}</div>
        {totalsStrip}
      </div>
    </div>
  )
}

function Cell({ label, value, strong }: { label: string; value: string; strong?: boolean }) {
  return (
    <span style={{ display: 'inline-flex', flexDirection: 'column', lineHeight: 1.2 }}>
      <span style={{ fontSize: 10, textTransform: 'uppercase', color: 'var(--text-muted, #666)' }}>{label}</span>
      <Pulse on={value}><span style={{ fontSize: strong ? 15 : 13, fontWeight: strong ? 700 : 500 }}>{value}</span></Pulse>
    </span>
  )
}
