'use client'

import { useEffect, useMemo, useState } from 'react'
import Link from 'next/link'
import { useSearchParams } from 'next/navigation'
import { DeleteForm } from '@/components/delete-form'
import { NewEntryRow } from './new-entry-row'
import {
  bulkApplyToInvoice,
  bulkRecalculate,
  bulkUpdateFields,
  deleteEntry,
  resolveEntry,
  updateEntry,
} from './actions'
import { bulkRemoveFromInvoice } from '../invoices/actions'
import { blockerReason } from '@/lib/toggl'
import { fmtHoursMinutesSeconds, fmtUsd } from '@/lib/format'
import { SortableHeader } from '@/components/sortable-header'
import type { TeamMemberOption } from '@/lib/teams'
import { SearchableCombobox } from '@/components/searchable-combobox'

export type Entry = {
  id: string
  import_id: string | null
  source_user_email: string | null
  source_user_name: string | null
  team_member_id: string | null
  operator: string | null
  client: string | null
  project: string | null
  description: string | null
  billable: boolean | null
  start_at: string
  end_at: string | null
  duration_seconds: number | null
  converted_user: string | null
  converted_duration_seconds: number | null
  billout_cost_usd: number | null
  /** Owner-only column. NULL when no billout rate was set at write time. */
  billout_amount_usd: number | null
  transferred_at: string | null
  transfer_batch_id: string | null
  /** Attached invoice (null = not yet applied to an invoice). */
  invoice_id: string | null
  /** Joined invoice name for display, if attached. */
  invoice_name: string | null
}

export type FilterSnapshot = {
  date?: string
  start?: string
  end?: string
  status?: string
  batch?: string
  q?: string
  project?: string
  user?: string
  source_email?: string
  client?: string
  operator?: string
  imported_by?: string
}

type Props = {
  entries: Entry[]
  totalCount: number
  editId: string | null
  canEdit: boolean
  canTransfer: boolean
  filter: FilterSnapshot
  batchLabel: Record<string, string>
  /** Existing operator names for the EditRow datalist. */
  operatorNames: string[]
  /** Existing client names (deduped across operators). */
  clientNames: string[]
  /** Existing project names (deduped across clients). */
  projectNames: string[]
  /**
   * Lowercase client-name → list of operators it appears under. Used by
   * the EditRow cascade: changing the client field to a known-unique
   * client auto-fills the operator field.
   */
  clientNameToOperators: Record<string, string[]>
  /**
   * Lowercase project-name → list of (operator, client) pairs it appears
   * under. Used by the EditRow cascade for similar auto-fill behavior.
   */
  projectNameToParents: Record<string, { operator: string; client: string }[]>
  /**
   * Lowercase operator-name → list of client names under it. Used to
   * STRICTLY narrow the Client datalist once an operator is picked.
   */
  clientsByOperator: Record<string, string[]>
  /**
   * Lowercase `${operator}|${client}` → list of project names under
   * that pair. Used to STRICTLY narrow the Project datalist once
   * operator + client are both picked.
   */
  projectsByClientPair: Record<string, string[]>
  /**
   * All team_members the caller can see, grouped per team. Populates the
   * "Team member" dropdown in EditRow + NewEntryRow — the canonical way
   * to change which person an entry is billed for. Replaces the old
   * model of editing `source_user_email` and re-resolving.
   */
  teamMembers: TeamMemberOption[]
  /**
   * Open + sent invoices for the bulk "Apply to invoice" picker. Empty
   * array → no invoices yet, the bulk action shows a friendly nudge.
   */
  invoiceOptions: { id: string; name: string; status: string }[]
  /**
   * Project entities for the bulk "Reassign project" picker. Label is
   * `{client} : {project}`; value is `projects.id`.
   */
  projectOptions: { id: string; label: string }[]
  /** When true, an empty "new entry" form row is rendered at the top. */
  showNewEntry: boolean
  /** Current user's email and full name — used as defaults in the new-entry form. */
  currentUserEmail: string
  currentUserName: string
}

// Two-digit year, 3-letter month, two-digit day. Time on hover (title).
const MONTHS_SHORT = [
  'Jan',
  'Feb',
  'Mar',
  'Apr',
  'May',
  'Jun',
  'Jul',
  'Aug',
  'Sep',
  'Oct',
  'Nov',
  'Dec',
]

function fmtDateCompact(ts: string): { label: string; full: string } {
  // Naive timestamp like "2026-05-22T09:30:00"
  const m = ts.match(/^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2})(?::(\d{2}))?/)
  if (!m) return { label: ts.slice(0, 10), full: ts }
  const [, year, month, day, hh, mm, ss] = m
  const yy = year.slice(-2)
  const mon = MONTHS_SHORT[Number(month) - 1] ?? month
  const label = `${yy}/${mon}/${day}`
  const full = `${day} ${MONTHS_SHORT[Number(month) - 1]} ${year}, ${hh}:${mm}${ss ? ':' + ss : ''}`
  return { label, full }
}

function StatusCell({ entry }: { entry: Entry }) {
  if (entry.invoice_id) {
    return (
      <span
        title={
          entry.invoice_name
            ? `Applied to invoice: ${entry.invoice_name}`
            : 'Applied to an invoice'
        }
        style={{ color: '#15803d', fontWeight: 600, fontSize: '1rem' }}
      >
        ✓
      </span>
    )
  }
  if (entry.transferred_at) {
    // Legacy: transferred row that escaped the invoice backfill. Still
    // locked, still treated as applied.
    return (
      <span
        title="Transferred (legacy — pre-invoice)"
        style={{ color: '#15803d', fontWeight: 600, fontSize: '1rem' }}
      >
        ✓
      </span>
    )
  }
  const reason = blockerReason(entry)
  if (reason) {
    return (
      <span
        title={`Blocked: ${reason}`}
        style={{ color: '#b91c1c', fontSize: '1rem' }}
      >
        ⚠
      </span>
    )
  }
  return (
    <span
      title="Pending — ready to apply to an invoice"
      style={{ color: '#777', fontSize: '1rem' }}
    >
      ○
    </span>
  )
}

function toDateTimeLocal(ts: string): string {
  return ts.replace(' ', 'T').slice(0, 16)
}

export function EntriesTable({
  entries,
  totalCount,
  editId,
  canEdit,
  canTransfer,
  filter,
  batchLabel,
  operatorNames,
  clientNames,
  projectNames,
  clientNameToOperators,
  projectNameToParents,
  clientsByOperator,
  projectsByClientPair,
  teamMembers,
  invoiceOptions,
  projectOptions,
  showNewEntry,
  currentUserEmail,
  currentUserName,
}: Props) {
  const params = useSearchParams()
  const [selected, setSelected] = useState<Set<string>>(new Set())
  const [selectAllMatching, setSelectAllMatching] = useState(false)
  const [bulkEditOpen, setBulkEditOpen] = useState(false)
  // Bulk reassign: state lives inside SearchableCombobox (it writes its
  // own hidden input). The parent reads those hidden inputs at confirm
  // time — see confirmBulkEdit below. Avoids duplicate state.
  // (decisions.md #042 explains why no free-text path.)

  const editHref = (id: string) => {
    const next = new URLSearchParams(params)
    next.set('edit', id)
    return `/entries?${next.toString()}`
  }
  const cancelHref = useMemo(() => {
    const next = new URLSearchParams(params)
    next.delete('edit')
    const qs = next.toString()
    return qs ? `/entries?${qs}` : '/entries'
  }, [params])
  const returnUrl = cancelHref

  const filterKey = JSON.stringify(filter)
  useEffect(() => {
    setSelected(new Set())
    setSelectAllMatching(false)
  }, [filterKey])

  const allOnPageSelected = useMemo(
    () => entries.length > 0 && entries.every((e) => selected.has(e.id)),
    [entries, selected],
  )

  const effectiveCount = selectAllMatching ? totalCount : selected.size
  const hasSelection = effectiveCount > 0

  function toggleOne(id: string) {
    if (selectAllMatching) {
      setSelectAllMatching(false)
      setSelected(new Set([id]))
      return
    }
    setSelected((prev) => {
      const next = new Set(prev)
      if (next.has(id)) next.delete(id)
      else next.add(id)
      return next
    })
  }

  function toggleAllOnPage() {
    if (selectAllMatching || allOnPageSelected) {
      setSelectAllMatching(false)
      setSelected(new Set())
    } else {
      setSelected(new Set(entries.map((e) => e.id)))
    }
  }

  function expandToAllMatching() {
    setSelectAllMatching(true)
    setSelected(new Set())
  }

  function clear() {
    setSelectAllMatching(false)
    setSelected(new Set())
    setBulkEditOpen(false)
  }

  function confirmBulkEdit(e: React.MouseEvent<HTMLButtonElement>) {
    // Read the combobox hidden inputs directly from the form. They're
    // the source of truth for the picked entity ids.
    const form = e.currentTarget.closest('form')
    const projectId =
      (form?.querySelector(
        'input[name="bulk_project_id"]',
      ) as HTMLInputElement | null)?.value ?? ''
    const memberId =
      (form?.querySelector(
        'input[name="bulk_team_member_id"]',
      ) as HTMLInputElement | null)?.value ?? ''

    if (!projectId && !memberId) {
      e.preventDefault()
      window.alert('Pick a project or a team member to reassign to.')
      return
    }
    const parts: string[] = []
    if (projectId) {
      const label = projectOptions.find((p) => p.id === projectId)?.label
      if (label) parts.push(`project → ${label}`)
    }
    if (memberId) {
      const member = teamMembers.find((m) => m.id === memberId)
      if (member) parts.push(`team member → ${member.display_name}`)
    }
    const summary = parts.join(' · ')
    if (
      !window.confirm(
        `Reassign ${effectiveCount} ${effectiveCount === 1 ? 'entry' : 'entries'}: ${summary}? ${canTransfer ? '' : 'Locked rows (attached to an invoice) are skipped.'}`,
      )
    ) {
      e.preventDefault()
    }
  }

  function confirmApply(e: React.MouseEvent<HTMLButtonElement>) {
    // Pull the invoice_id from the inline select to make the confirm
    // message specific.
    const form = e.currentTarget.closest('form') as HTMLFormElement | null
    const select = form?.querySelector(
      'select[name="invoice_id"]',
    ) as HTMLSelectElement | null
    if (!select || !select.value) {
      e.preventDefault()
      window.alert('Pick an invoice from the dropdown first.')
      return
    }
    const invoiceLabel =
      select.options[select.selectedIndex]?.text ?? 'the picked invoice'
    if (
      !window.confirm(
        `Apply ${effectiveCount} entries to "${invoiceLabel}"? They'll be locked — only an owner can detach them.`,
      )
    ) {
      e.preventDefault()
    }
  }

  function confirmDetach(e: React.MouseEvent<HTMLButtonElement>) {
    if (selectAllMatching) {
      e.preventDefault()
      window.alert(
        'Detach only works on an explicit selection. Uncheck "select all matching" and pick rows.',
      )
      return
    }
    if (
      !window.confirm(
        `Detach ${effectiveCount} entries from their invoice? They'll go back to unlocked / Pending.`,
      )
    ) {
      e.preventDefault()
    }
  }

  function confirmRecalculate(e: React.MouseEvent<HTMLButtonElement>) {
    if (
      !window.confirm(
        `Recalculate cost + billout for ${effectiveCount} entries using current rates and project overrides?`,
      )
    ) {
      e.preventDefault()
    }
  }

  function bulkHiddenInputs() {
    if (selectAllMatching) {
      return (
        <>
          <input type="hidden" name="all_matching" value="1" />
          {Object.entries(filter).map(([k, v]) =>
            v ? (
              <input
                key={k}
                type="hidden"
                name={`filter_${k}`}
                value={String(v)}
              />
            ) : null,
          )}
        </>
      )
    }
    return [...selected].map((id) => (
      <input key={id} type="hidden" name="id" value={id} />
    ))
  }

  return (
    <>
      {/* Bulk-action form lives OUTSIDE the table so the per-row EditRow's
          own <form> isn't nested inside this one (HTML forbids nesting
          and silently drops the inner form, which broke Save). */}
      {hasSelection && (
        <form>
        {bulkHiddenInputs()}
        <div
          style={{
            background: '#1a1a1a',
            color: '#fff',
            padding: '0.65rem 1rem',
            borderRadius: '8px 8px 0 0',
            display: 'flex',
            justifyContent: 'space-between',
            alignItems: 'center',
            flexWrap: 'wrap',
            gap: '0.75rem',
          }}
        >
          <div>
            <strong>{effectiveCount}</strong>{' '}
            {effectiveCount === 1 ? 'entry' : 'entries'} selected
            {!selectAllMatching &&
              allOnPageSelected &&
              entries.length < totalCount && (
                <>
                  {' · '}
                  <button
                    type="button"
                    onClick={expandToAllMatching}
                    className="link-button"
                    style={{ color: '#9ec5ff' }}
                  >
                    Select all {totalCount} matching the filter
                  </button>
                </>
              )}
            {selectAllMatching && (
              <span style={{ color: '#aaa', marginLeft: '0.5rem' }}>
                (all matching the current filter)
              </span>
            )}
          </div>
          <div className="flex-row" style={{ gap: '0.5rem', flexWrap: 'wrap' }}>
            {canTransfer && (
              <>
                {invoiceOptions.length === 0 ? (
                  <span
                    className="muted"
                    style={{ fontSize: '0.85em', alignSelf: 'center' }}
                  >
                    No open invoices.{' '}
                    <Link href="/invoices?new=1" style={{ color: '#9ec5ff' }}>
                      Create one →
                    </Link>
                  </span>
                ) : (
                  <>
                    <select
                      name="invoice_id"
                      defaultValue=""
                      style={{
                        background: '#0a0a0a',
                        color: '#fff',
                        border: '1px solid #333',
                        padding: '0.3rem 0.5rem',
                        fontSize: '0.85rem',
                      }}
                    >
                      <option value="">— pick an invoice —</option>
                      {invoiceOptions.map((inv) => (
                        <option key={inv.id} value={inv.id}>
                          {inv.name}
                          {inv.status !== 'open' ? ` (${inv.status})` : ''}
                        </option>
                      ))}
                    </select>
                    <button
                      type="submit"
                      formAction={bulkApplyToInvoice}
                      onClick={confirmApply}
                    >
                      Apply to invoice
                    </button>
                  </>
                )}
                <button
                  type="submit"
                  formAction={bulkRemoveFromInvoice}
                  onClick={confirmDetach}
                  className="secondary"
                  title="Remove the selected entries from whatever invoice they're attached to."
                >
                  Detach
                </button>
                <button
                  type="submit"
                  formAction={bulkRecalculate}
                  onClick={confirmRecalculate}
                  className="secondary"
                  title="Re-stamp cost + billout using current rates and project overrides."
                >
                  Recalculate
                </button>
              </>
            )}
            <button
              type="button"
              onClick={() => setBulkEditOpen((v) => !v)}
              className="secondary"
            >
              Edit fields {bulkEditOpen ? '▴' : '▾'}
            </button>
            <button type="button" onClick={clear} className="secondary">
              Clear
            </button>
          </div>
        </div>
        {bulkEditOpen && (
        <div
          style={{
            background: '#2a2a2a',
            color: '#fff',
            padding: '0.65rem 1rem',
            borderBottom: '1px solid #000',
            display: 'flex',
            gap: '0.75rem',
            flexWrap: 'wrap',
            alignItems: 'end',
          }}
        >
          <input
            type="hidden"
            name="expected_count"
            value={String(effectiveCount)}
          />
          <label style={{ flex: '2 1 18rem', minWidth: 0, color: '#d0d0d0' }}>
            <span style={{ fontSize: '0.75rem' }}>
              Reassign project{' '}
              <span style={{ color: '#888' }}>
                ({'{client}'} : {'{project}'})
              </span>
            </span>
            <SearchableCombobox
              name="bulk_project_id"
              options={projectOptions}
              placeholder="type to search projects…"
              dark
            />
          </label>
          <label style={{ flex: '2 1 18rem', minWidth: 0, color: '#d0d0d0' }}>
            <span style={{ fontSize: '0.75rem' }}>
              Reassign team member{' '}
              <span style={{ color: '#888' }}>
                ({'{name}'} : {'{email}'})
              </span>
            </span>
            <SearchableCombobox
              name="bulk_team_member_id"
              options={teamMembers.map((m) => ({
                id: m.id,
                label: `${m.display_name} : ${m.email}`,
              }))}
              placeholder="type to search team members…"
              dark
            />
          </label>
          <button
            type="submit"
            formAction={bulkUpdateFields}
            onClick={confirmBulkEdit}
            style={{ alignSelf: 'end' }}
          >
            Reassign {effectiveCount}
          </button>
        </div>
        )}
        </form>
      )}

      <div
        className="table-wrap"
        style={{ borderRadius: hasSelection ? '0 0 8px 8px' : '8px' }}
      >
        <table className="data">
          <thead>
            <tr>
              <th style={{ width: '2rem' }}>
                <input
                  type="checkbox"
                  checked={allOnPageSelected || selectAllMatching}
                  onChange={toggleAllOnPage}
                  aria-label="Select all on this page"
                />
              </th>
              <th style={{ width: '2rem' }}>
                <SortableHeader sortKey="status">St.</SortableHeader>
              </th>
              <th>
                <SortableHeader sortKey="date" defaultDir="desc">
                  Date
                </SortableHeader>
              </th>
              <th>
                <SortableHeader sortKey="user">User</SortableHeader>
              </th>
              <th>
                <SortableHeader sortKey="operator">Operator</SortableHeader>
              </th>
              <th>
                <SortableHeader sortKey="client">Client</SortableHeader>
              </th>
              <th>
                <SortableHeader sortKey="project">Project</SortableHeader>
              </th>
              <th>
                <SortableHeader sortKey="description">Description</SortableHeader>
              </th>
              <th className="num">
                <SortableHeader sortKey="source_hrs" numeric>
                  Hours
                </SortableHeader>
              </th>
              <th className="num">
                <SortableHeader sortKey="cost" numeric>
                  Cost
                </SortableHeader>
              </th>
              {canTransfer && (
                <th
                  className="num"
                  title="Owner-only: what we charge the client for this person's hours."
                >
                  <SortableHeader sortKey="billout_amount" numeric>
                    Billout
                  </SortableHeader>
                </th>
              )}
              <th />
            </tr>
          </thead>
          <tbody>
            {showNewEntry && canEdit && (
              <NewEntryRow
                operatorNames={operatorNames}
                clientNames={clientNames}
                projectNames={projectNames}
                clientNameToOperators={clientNameToOperators}
                projectNameToParents={projectNameToParents}
                clientsByOperator={clientsByOperator}
                projectsByClientPair={projectsByClientPair}
                teamMembers={teamMembers}
                defaultEmail={currentUserEmail}
                defaultName={currentUserName}
                colSpan={canTransfer ? 12 : 11}
              />
            )}
            {entries.map((e) =>
              e.id === editId && canEdit ? (
                <EditRow
                  key={e.id}
                  entry={e}
                  cancelHref={cancelHref}
                  returnUrl={returnUrl}
                  batchLabel={batchLabel}
                  operatorNames={operatorNames}
                  clientNames={clientNames}
                  projectNames={projectNames}
                  clientNameToOperators={clientNameToOperators}
                  projectNameToParents={projectNameToParents}
                  clientsByOperator={clientsByOperator}
                  projectsByClientPair={projectsByClientPair}
                  teamMembers={teamMembers}
                />
              ) : (
                <DataRow
                  key={e.id}
                  entry={e}
                  selected={selectAllMatching || selected.has(e.id)}
                  onToggle={() => toggleOne(e.id)}
                  editHref={editHref(e.id)}
                  canEdit={canEdit}
                  canTransfer={canTransfer}
                />
              ),
            )}
          </tbody>
        </table>
      </div>
    </>
  )
}

function DataRow({
  entry,
  selected,
  onToggle,
  editHref,
  canEdit,
  canTransfer,
}: {
  entry: Entry
  selected: boolean
  onToggle: () => void
  editHref: string
  canEdit: boolean
  canTransfer: boolean
}) {
  const date = fmtDateCompact(entry.start_at)
  const userTitle = entry.source_user_email
    ? `source: ${entry.source_user_email}`
    : undefined
  const locked = entry.invoice_id != null || entry.transferred_at != null
  const rowClass = locked
    ? 'row-transferred'
    : blockerReason(entry)
      ? 'row-blocked'
      : undefined

  return (
    <tr className={rowClass}>
      <td>
        <input
          type="checkbox"
          checked={selected}
          onChange={onToggle}
          aria-label={`Select ${entry.id}`}
        />
      </td>
      <td style={{ textAlign: 'center' }}>
        <StatusCell entry={entry} />
      </td>
      <td title={date.full} style={{ whiteSpace: 'nowrap' }}>
        {date.label}
      </td>
      <td title={userTitle}>
        {entry.converted_user || <span className="muted">—</span>}
      </td>
      <td>{entry.operator || <span className="muted">—</span>}</td>
      <td>{entry.client || <span className="muted">—</span>}</td>
      <td>{entry.project || <span className="muted">—</span>}</td>
      <td>{entry.description || <span className="muted">—</span>}</td>
      <td className="num">{fmtHoursMinutesSeconds(entry.duration_seconds)}</td>
      <td className="num">
        {entry.billout_cost_usd != null
          ? fmtUsd(Number(entry.billout_cost_usd))
          : '—'}
      </td>
      {canTransfer && (
        <td className="num">
          {entry.billout_amount_usd != null ? (
            fmtUsd(Number(entry.billout_amount_usd))
          ) : (
            <span className="muted">—</span>
          )}
        </td>
      )}
      <td className="right">
        {canEdit && (!locked || canTransfer) && (
          <div
            className="flex-row"
            style={{ justifyContent: 'flex-end', gap: '0.5rem' }}
          >
            <Link
              href={editHref}
              scroll={false}
              title="Edit entry"
              style={{ textDecoration: 'none', fontSize: '1rem' }}
            >
              ✏️
            </Link>
            <DeleteForm
              action={deleteEntry}
              id={entry.id}
              confirmText="Delete this entry? Cannot be undone."
              label="🗑️"
            />
          </div>
        )}
        {canEdit && locked && !canTransfer && (
          <span
            className="muted"
            style={{ fontSize: '0.85em' }}
            title="Entries attached to an invoice are locked. Only an owner can edit."
          >
            🔒
          </span>
        )}
      </td>
    </tr>
  )
}

function EditRow({
  entry,
  cancelHref,
  returnUrl,
  batchLabel,
  operatorNames,
  clientNames,
  projectNames,
  clientNameToOperators,
  projectNameToParents,
  clientsByOperator,
  projectsByClientPair,
  teamMembers,
}: {
  entry: Entry
  cancelHref: string
  returnUrl: string
  batchLabel: Record<string, string>
  operatorNames: string[]
  clientNames: string[]
  projectNames: string[]
  clientNameToOperators: Record<string, string[]>
  projectNameToParents: Record<string, { operator: string; client: string }[]>
  clientsByOperator: Record<string, string[]>
  projectsByClientPair: Record<string, string[]>
  teamMembers: TeamMemberOption[]
}) {
  // Stable datalist IDs scoped to this row so multiple EditRows on the
  // same page (rare but possible) don't collide.
  const opListId = `op-list-${entry.id}`
  const clListId = `cl-list-${entry.id}`
  const prListId = `pr-list-${entry.id}`

  // Cascading entity inheritance. The cascade fires on **blur** so a
  // mid-typing partial value doesn't blow away the user's other fields.
  // Rules (per user request):
  //   - Change OPERATOR (any new value, known or not) → clear client + project.
  //   - Change CLIENT → clear project; if the new client name has exactly
  //     ONE matching operator, auto-fill the operator field.
  //   - Change PROJECT → if the new project name has exactly ONE
  //     matching (op, client) pair, auto-fill both.
  // Ambiguous matches (e.g. "Website" exists under 4 different clients)
  // do NOT auto-fill — user keeps their current op/client and the server
  // resolves under that tuple on save.
  const [operatorVal, setOperatorVal] = useState(entry.operator ?? '')
  const [clientVal, setClientVal] = useState(entry.client ?? '')
  const [projectVal, setProjectVal] = useState(entry.project ?? '')

  // Datalist sources, narrowed by the currently-picked parent. When
  // an operator is set AND matches a known operator, the client
  // datalist shows only that operator's clients. Same for project
  // datalist scoped to the (operator, client) pair. Without this,
  // duplicate-named projects across clients (e.g. "10 Hour Support
  // Block") are indistinguishable in the dropdown and the user can
  // silently pick one belonging to a different client.
  const opKey = operatorVal.trim().toLowerCase()
  const clKey = clientVal.trim().toLowerCase()
  const filteredClientNames = opKey
    ? (clientsByOperator[opKey] ?? [])
    : clientNames
  const filteredProjectNames =
    opKey && clKey
      ? (projectsByClientPair[`${opKey}|${clKey}`] ?? [])
      : projectNames

  function onOperatorBlur() {
    if (operatorVal !== (entry.operator ?? '')) {
      setClientVal('')
      setProjectVal('')
    }
  }
  function onClientBlur() {
    if (clientVal === (entry.client ?? '')) return
    const matches = clientNameToOperators[clientVal.toLowerCase()] ?? []
    if (matches.length === 1) setOperatorVal(matches[0])
    setProjectVal('')
  }
  function onProjectBlur() {
    if (projectVal === (entry.project ?? '')) return
    const matches =
      projectNameToParents[projectVal.toLowerCase()] ?? []
    if (matches.length === 1) {
      setOperatorVal(matches[0].operator)
      setClientVal(matches[0].client)
    }
  }
  const labelStyle: React.CSSProperties = {
    fontSize: '0.78rem',
    gap: '0.2rem',
  }
  const inputStyle: React.CSSProperties = {
    padding: '0.35rem 0.5rem',
    fontSize: '0.85rem',
  }
  return (
    <tr style={{ background: '#fff8e1' }}>
      <td colSpan={13} style={{ padding: '0.6rem 0.75rem' }}>
        <form action={updateEntry}>
          <input type="hidden" name="id" value={entry.id} />
          <input type="hidden" name="_return_url" value={returnUrl} />
          <div
            style={{
              display: 'grid',
              gridTemplateColumns: 'repeat(4, minmax(0, 1fr))',
              gap: '0.4rem 0.75rem',
              marginBottom: '0.5rem',
            }}
          >
            <label style={labelStyle}>
              Operator{' '}
              <span className="muted">
                (change clears client + project)
              </span>
              <input
                name="operator"
                type="text"
                list={opListId}
                autoComplete="off"
                value={operatorVal}
                onChange={(e) => setOperatorVal(e.target.value)}
                onBlur={onOperatorBlur}
                style={inputStyle}
              />
              <datalist id={opListId}>
                {operatorNames.map((n) => (
                  <option key={n} value={n} />
                ))}
              </datalist>
            </label>
            <label style={labelStyle}>
              Client{' '}
              <span className="muted">(auto-fills operator)</span>
              <input
                name="client"
                type="text"
                list={clListId}
                autoComplete="off"
                value={clientVal}
                onChange={(e) => setClientVal(e.target.value)}
                onBlur={onClientBlur}
                style={inputStyle}
              />
              <datalist id={clListId}>
                {filteredClientNames.map((n) => (
                  <option key={n} value={n} />
                ))}
              </datalist>
            </label>
            <label style={labelStyle}>
              Project{' '}
              <span className="muted">(auto-fills both)</span>
              <input
                name="project"
                type="text"
                list={prListId}
                autoComplete="off"
                value={projectVal}
                onChange={(e) => setProjectVal(e.target.value)}
                onBlur={onProjectBlur}
                style={inputStyle}
              />
              <datalist id={prListId}>
                {filteredProjectNames.map((n) => (
                  <option key={n} value={n} />
                ))}
              </datalist>
            </label>
            <label style={labelStyle}>
              Team member{' '}
              <span className="muted">(picks the cost / billout rate)</span>
              <select
                name="team_member_id"
                defaultValue={entry.team_member_id ?? ''}
                style={inputStyle}
              >
                <option value="">— unresolved —</option>
                {Object.entries(
                  teamMembers.reduce<Record<string, TeamMemberOption[]>>(
                    (acc, m) => {
                      const key = m.team_name ?? '(unnamed team)'
                      ;(acc[key] ??= []).push(m)
                      return acc
                    },
                    {},
                  ),
                ).map(([teamName, members]) => (
                  <optgroup key={teamName} label={teamName}>
                    {members.map((m) => (
                      <option key={m.id} value={m.id}>
                        {m.display_name}
                        {m.consolidate_as ? ` → ${m.consolidate_as}` : ''}
                        {m.is_active ? '' : ' (inactive)'}
                        {' · '}
                        {m.email}
                      </option>
                    ))}
                  </optgroup>
                ))}
              </select>
            </label>

            <div
              style={{
                ...labelStyle,
                background: '#f5f5f5',
                padding: '0.45rem 0.6rem',
                borderRadius: 4,
                color: '#555',
                fontSize: '0.75rem',
                lineHeight: 1.3,
              }}
              title="Original Clockify identity. Read-only — change Team member above to redirect billing."
            >
              <span style={{ fontWeight: 600, color: '#777' }}>
                Logged as (audit)
              </span>
              <span>
                {entry.source_user_name ?? <em>—</em>}
                {entry.source_user_email ? (
                  <>
                    {' · '}
                    <span className="muted">{entry.source_user_email}</span>
                  </>
                ) : null}
              </span>
            </div>
            <label style={labelStyle}>
              Start
              <input
                name="start_at"
                type="datetime-local"
                step="60"
                defaultValue={toDateTimeLocal(entry.start_at)}
                required
                style={inputStyle}
              />
            </label>
            <label style={labelStyle}>
              End
              <input
                name="end_at"
                type="datetime-local"
                step="60"
                defaultValue={entry.end_at ? toDateTimeLocal(entry.end_at) : ''}
                style={inputStyle}
              />
            </label>

            <label style={labelStyle}>
              Source dur <span className="muted">(h or hh:mm:ss)</span>
              <input
                name="duration_seconds"
                type="text"
                defaultValue={
                  entry.duration_seconds != null
                    ? (entry.duration_seconds / 3600).toFixed(4)
                    : ''
                }
                style={inputStyle}
              />
            </label>
            <label style={labelStyle}>
              Adi dur <span className="muted">(h or hh:mm:ss)</span>
              <input
                name="converted_duration_seconds"
                type="text"
                defaultValue={
                  entry.converted_duration_seconds != null
                    ? (entry.converted_duration_seconds / 3600).toFixed(4)
                    : ''
                }
                style={inputStyle}
              />
            </label>
            <label
              style={{
                ...labelStyle,
                flexDirection: 'row',
                alignItems: 'center',
                gap: '0.4rem',
                paddingTop: '1rem',
              }}
            >
              <input
                name="billable"
                type="checkbox"
                defaultChecked={entry.billable ?? false}
              />
              Billable
            </label>
            <label
              style={{
                ...labelStyle,
                color: '#777',
              }}
            >
              Batch <span className="muted">(read-only)</span>
              <input
                type="text"
                value={
                  entry.import_id
                    ? batchLabel[entry.import_id] ?? entry.import_id.slice(0, 8)
                    : '—'
                }
                readOnly
                disabled
                style={{ ...inputStyle, background: '#f7f7f7' }}
              />
            </label>

            <label style={{ ...labelStyle, gridColumn: 'span 4' }}>
              Description
              <input
                name="description"
                type="text"
                defaultValue={entry.description ?? ''}
                style={inputStyle}
              />
            </label>
          </div>
          <div className="flex-row" style={{ gap: '0.4rem' }}>
            <button type="submit" style={{ padding: '0.4rem 0.85rem' }}>
              Save
            </button>
            <Link
              href={cancelHref}
              scroll={false}
              className="button-link-secondary"
              style={{ padding: '0.4rem 0.85rem', fontSize: '0.9rem' }}
            >
              Cancel
            </Link>
            <button
              type="submit"
              formAction={resolveEntry}
              className="secondary"
              style={{ padding: '0.4rem 0.85rem' }}
              title="Re-run the team-lookup conversion using the current source email"
            >
              Re-resolve from team
            </button>
          </div>
        </form>
      </td>
    </tr>
  )
}
