﻿import { useEffect, useRef, useState, type ReactNode } from 'react'
import {
  bulkDeleteProcess,
  bulkUpdateProcess,
  deleteProcessItem,
  getProcessItem,
  getProcessPage,
  importProcess,
  processDownloadUrl,
  updateProcessing,
  type Org,
  type ProcessImportReport,
  type ProcessItem,
  type Code,
  type ProcessStatusFilter,
  type ProcessingRequest,
} from '../api/client'
import { useCodes, useSplitByFirstRow } from '../codes/useCodes'

const PAGE_SIZE = 30

/** 공공누리 유형이 쓰이는 자리. 권리처리 판정에는 '보류'가 없다. */
const SCOPE_PROCESS = 'process'

/** 자유 입력이 붙는 계약서류('기타'). 저장값이 "기타:본문" 형태가 된다. */
function etcDocOf(docs: Code[]): Code | undefined {
  return docs.find((doc) => doc.attrs.freeText === true)
}

/** '처리완료'에 해당하는 코드. 처리일시를 찍고 배지를 초록으로 바꾸는 기준이다. */
function doneStatusOf(statuses: Code[]): Code | undefined {
  return statuses.find((status) => status.attrs.done === true)
}

/** "양도계약서, 공문, 기타:직접 촬영본 사용동의" 형태의 저장값 → 체크박스 상태로 역파싱한다. */
function parseContractDocs(
  value: string | null,
  docs: Code[],
): { selected: Set<string>; etcText: string } {
  const selected = new Set<string>()
  let etcText = ''
  if (!value) {
    return { selected, etcText }
  }
  const etc = etcDocOf(docs)
  for (const raw of value.split(',')) {
    const part = raw.trim()
    if (!part) {
      continue
    }
    if (etc && part.startsWith(`${etc.code}:`)) {
      selected.add(etc.code)
      etcText = part.slice(etc.code.length + 1)
    } else if (docs.some((doc) => doc.code === part)) {
      selected.add(part)
    }
  }
  return { selected, etcText }
}

/** 체크박스 상태 → 저장값. 아무것도 선택하지 않았으면 null. */
function serializeContractDocs(
  selected: Set<string>,
  etcText: string,
  docs: Code[],
): string | null {
  const etc = etcDocOf(docs)
  const parts: string[] = docs
    .filter((doc) => doc.attrs.freeText !== true && selected.has(doc.code))
    .map((doc) => doc.code)
  if (etc && selected.has(etc.code)) {
    parts.push(`${etc.code}:${etcText}`)
  }
  return parts.length > 0 ? parts.join(', ') : null
}

function formatDate(ms: number | null): string {
  if (!ms) {
    return '-'
  }
  const d = new Date(ms)
  const y = d.getFullYear()
  const m = String(d.getMonth() + 1).padStart(2, '0')
  const day = String(d.getDate()).padStart(2, '0')
  return `${y}-${m}-${day}`
}

function truncate(text: string | null, max: number): string {
  if (!text) {
    return '-'
  }
  return text.length > max ? `${text.slice(0, max)}…` : text
}

/** 소제목: 작은 파란 사각 불릿 + 굵은 제목. */
function SectionHeader({ title }: { title: string }) {
  return (
    <h3 className="flex items-center gap-2 text-sm font-bold">
      <span className="h-2.5 w-2.5 shrink-0 bg-blue-700" />
      {title}
    </h3>
  )
}

/** 정보 테이블의 회색 라벨 셀. required면 라벨 앞에 빨간 별표를 붙인다. */
function LabelCell({ children, required }: { children: ReactNode; required?: boolean }) {
  return (
    <div className="flex items-center bg-gray-50 px-3 py-2 text-xs font-medium text-gray-600">
      {required && <span className="text-red-500">*</span>}
      {children}
    </div>
  )
}

/** 정보 테이블의 값 셀. */
function ValueCell({ children }: { children: ReactNode }) {
  return <div className="flex items-center px-3 py-1.5">{children}</div>
}

/** 읽기전용 값 표시용 회색 박스. */
function ReadonlyBox({ value }: { value: ReactNode }) {
  return (
    <div className="w-full rounded border border-gray-200 bg-gray-100 px-3 py-1.5 text-sm text-gray-800">
      {value || <span className="text-gray-400">-</span>}
    </div>
  )
}

/** 단일 라벨/값 행(150px + 1fr). */
function InfoRow({
  label,
  required,
  children,
}: {
  label: string
  required?: boolean
  children: ReactNode
}) {
  return (
    <div className="grid grid-cols-[150px_1fr]">
      <LabelCell required={required}>{label}</LabelCell>
      <ValueCell>{children}</ValueCell>
    </div>
  )
}

/** 라벨/값 두 쌍을 한 행에 나란히 배치(150px + 1fr + 150px + 1fr). */
function InfoRowPair({
  leftLabel,
  left,
  rightLabel,
  right,
}: {
  leftLabel: string
  left: ReactNode
  rightLabel: string
  right: ReactNode
}) {
  return (
    <div className="grid grid-cols-[150px_1fr_150px_1fr]">
      <LabelCell>{leftLabel}</LabelCell>
      <ValueCell>{left}</ValueCell>
      <LabelCell>{rightLabel}</LabelCell>
      <ValueCell>{right}</ValueCell>
    </div>
  )
}

function ProcessStatusBadge({ value }: { value: string | null }) {
  // 어느 값이 '완료'인지는 코드표의 done 속성이 정한다.
  const statuses = useCodes('PROCESS_STATUS')
  const doneCode = doneStatusOf(statuses)
  const done = !!doneCode && value === doneCode.code
  const pendingLabel = statuses.find((status) => status.attrs.done !== true)?.label ?? ''
  return (
    <span
      className={`rounded-full px-2 py-0.5 text-xs font-medium ${
        done ? 'bg-emerald-50 text-emerald-700' : 'bg-gray-100 text-gray-500'
      }`}
    >
      {done ? doneCode.label : pendingLabel}
    </span>
  )
}

/** 기관관리 상세의 권리처리 탭. 목록↔상세 두 화면을 이 컴포넌트 하나에서 전환한다(ReviewBoard와 동일 구조). */
export default function ProcessBoard({ org }: { org: Org }) {
  const statuses = useCodes('PROCESS_STATUS')
  const doneStatusCode = doneStatusOf(statuses)?.code
  const koglProcess = useSplitByFirstRow('KOGL_TYPE', SCOPE_PROCESS)
  // 필터 칩의 '전체'는 코드가 아니라 화면에서만 쓰는 항목이라 앞에 붙인다.
  // 나머지는 코드표에서 오되, 서버에 넘기는 키(PENDING/DONE)는 done 속성으로 정한다.
  const statusChips: { key: ProcessStatusFilter | undefined; label: string }[] = [
    { key: undefined, label: '전체' },
    ...statuses.map((status) => ({
      key: (status.attrs.done === true ? 'DONE' : 'PENDING') as ProcessStatusFilter,
      label: status.label,
    })),
  ]

  const [mode, setMode] = useState<'list' | number>('list')

  const [items, setItems] = useState<ProcessItem[]>([])
  const [total, setTotal] = useState(0)
  const [done, setDone] = useState(0)
  const [page, setPage] = useState(0)
  const [statusFilter, setStatusFilter] = useState<ProcessStatusFilter | undefined>(undefined)
  const [keywordInput, setKeywordInput] = useState('')
  const [appliedKeyword, setAppliedKeyword] = useState('')
  /** 복합검색(열별 조건). 서로 AND로 겹친다. */
  const [advancedOpen, setAdvancedOpen] = useState(false)
  const [siteInput, setSiteInput] = useState('')
  const [boardInput, setBoardInput] = useState('')
  const [titleInput, setTitleInput] = useState('')
  const [applied, setApplied] = useState({ site: '', board: '', title: '' })
  /** 일괄 등록/삭제 대상. */
  const [selected, setSelected] = useState<number[]>([])
  const [bulkStatus, setBulkStatus] = useState('')
  const [bulkKoglType, setBulkKoglType] = useState('')
  const [bulkOpinion, setBulkOpinion] = useState('')
  const [bulkBusy, setBulkBusy] = useState(false)
  const [bulkMessage, setBulkMessage] = useState<string | null>(null)
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState<string | null>(null)
  const [reloadKey, setReloadKey] = useState(0)

  const importInputRef = useRef<HTMLInputElement>(null)
  const [importBusy, setImportBusy] = useState(false)
  const [importReport, setImportReport] = useState<ProcessImportReport | null>(null)
  const [importError, setImportError] = useState<string | null>(null)

  useEffect(() => {
    if (mode !== 'list') {
      return
    }
    let cancelled = false
    setLoading(true)
    setError(null)

    getProcessPage(org.id, {
      keyword: appliedKeyword || undefined,
      site: applied.site || undefined,
      board: applied.board || undefined,
      title: applied.title || undefined,
      status: statusFilter,
      page,
      size: PAGE_SIZE,
    })
      .then((data) => {
        if (cancelled) {
          return
        }
        setItems(data.items)
        setTotal(data.total)
        setDone(data.done)
      })
      .catch((e: Error) => {
        if (!cancelled) {
          setError(e.message)
        }
      })
      .finally(() => {
        if (!cancelled) {
          setLoading(false)
        }
      })

    return () => {
      cancelled = true
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [mode, org.id, appliedKeyword, applied, statusFilter, page, reloadKey])

  function handleSearchFields() {
    setApplied({ site: siteInput.trim(), board: boardInput.trim(), title: titleInput.trim() })
  }

  function handleSearch() {
    handleSearchFields()
    setPage(0)
    setAppliedKeyword(keywordInput.trim())
  }

  function handleResetSearch() {
    setKeywordInput('')
    setSiteInput('')
    setBoardInput('')
    setTitleInput('')
    setPage(0)
    setAppliedKeyword('')
    setApplied({ site: '', board: '', title: '' })
  }

  function pickStatusChip(key: ProcessStatusFilter | undefined) {
    setPage(0)
    setStatusFilter(key)
  }

  async function handleImportFile(file: File) {
    setImportBusy(true)
    setImportError(null)
    try {
      const report = await importProcess(org.id, file)
      setImportReport(report)
      setReloadKey((k) => k + 1)
    } catch (e) {
      setImportError(e instanceof Error ? e.message : '엑셀 업로드에 실패했습니다.')
    } finally {
      setImportBusy(false)
    }
  }

  const allChecked = items.length > 0 && items.every((item) => selected.includes(item.id))

  function toggleAll() {
    setSelected(allChecked ? [] : items.map((item) => item.id))
  }

  function toggleOne(id: number) {
    setSelected((prev) => (prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]))
  }

  /** 체크한 건들에 채운 항목만 한 번에 반영한다. */
  async function handleBulkApply() {
    if (selected.length === 0 || bulkBusy) {
      return
    }
    if (!bulkStatus && !bulkKoglType && !bulkOpinion.trim()) {
      setBulkMessage('반영할 값을 하나 이상 채워주세요.')
      return
    }
    setBulkBusy(true)
    setBulkMessage(null)
    try {
      const res = await bulkUpdateProcess(org.id, {
        ids: selected,
        processStatus: bulkStatus || undefined,
        judgedKoglType: bulkKoglType || undefined,
        finalOpinion: bulkOpinion.trim() || undefined,
      })
      setBulkMessage(`${res.updated}건에 반영했습니다.`)
      setSelected([])
      setBulkStatus('')
      setBulkKoglType('')
      setBulkOpinion('')
      setReloadKey((k) => k + 1)
    } catch (e) {
      setBulkMessage(e instanceof Error ? e.message : '일괄 등록에 실패했습니다.')
    } finally {
      setBulkBusy(false)
    }
  }

  async function handleBulkDelete() {
    if (selected.length === 0 || bulkBusy) {
      return
    }
    if (!window.confirm(`선택한 ${selected.length}건을 삭제할까요?`)) {
      return
    }
    setBulkBusy(true)
    setBulkMessage(null)
    try {
      const res = await bulkDeleteProcess(org.id, selected)
      setBulkMessage(`${res.deleted}건을 삭제했습니다.`)
      setSelected([])
      setReloadKey((k) => k + 1)
    } catch (e) {
      setBulkMessage(e instanceof Error ? e.message : '삭제에 실패했습니다.')
    } finally {
      setBulkBusy(false)
    }
  }

  if (mode !== 'list') {
    return (
      <ProcessDetail
        org={org}
        itemId={mode}
        onBack={() => setMode('list')}
        onSaved={() => {
          setMode('list')
          setReloadKey((k) => k + 1)
        }}
      />
    )
  }

  const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE))

  return (
    <div className="p-4">
      <div className="flex flex-wrap items-center justify-between gap-3">
        <div className="flex flex-wrap items-center gap-2">
          <div className="flex items-center gap-1" role="group" aria-label="처리상태 필터">
            {statusChips.map((chip) => (
              <button
                key={chip.label}
                type="button"
                onClick={() => pickStatusChip(chip.key)}
                aria-pressed={statusFilter === chip.key}
                className={`rounded-full border px-3 py-1 text-xs font-medium ${
                  statusFilter === chip.key
                    ? 'border-blue-400 bg-blue-50 text-blue-700'
                    : 'border-gray-200 text-gray-500 hover:bg-gray-50'
                }`}
              >
                {chip.label}
              </button>
            ))}
          </div>

          <input
            type="text"
            aria-label="검색어"
            value={keywordInput}
            onChange={(e) => setKeywordInput(e.target.value)}
            onKeyDown={(e) => {
              if (e.key === 'Enter') {
                handleSearch()
              }
            }}
            placeholder="사이트명·게시물제목 검색"
            className="w-64 rounded-md border border-gray-300 px-2 py-1.5 text-sm"
          />
          <button
            type="button"
            aria-expanded={advancedOpen}
            onClick={() => setAdvancedOpen((v) => !v)}
            className="rounded-md border border-gray-300 px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-50"
          >
            상세검색
          </button>
          <button
            type="button"
            onClick={handleSearch}
            className="rounded-md border border-gray-300 px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-50"
          >
            검색
          </button>
          <button
            type="button"
            onClick={handleResetSearch}
            className="rounded-md border border-gray-300 px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-50"
          >
            초기화
          </button>
        </div>

        <div className="flex items-center gap-2">
          <input
            ref={importInputRef}
            type="file"
            accept=".xlsx,.xlsm"
            className="hidden"
            onChange={(e) => {
              const file = e.target.files?.[0]
              if (file) {
                void handleImportFile(file)
              }
              e.target.value = ''
            }}
          />
          <button
            type="button"
            disabled={importBusy}
            onClick={() => importInputRef.current?.click()}
            className="rounded-md border border-gray-300 px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-50 disabled:opacity-50"
          >
            {importBusy ? '업로드 중…' : '엑셀 업로드'}
          </button>
          <a
            href={processDownloadUrl(org.id)}
            className="rounded-md border border-gray-300 px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-50"
          >
            엑셀 다운로드
          </a>
        </div>
      </div>

      {importReport && (
        <p className="mt-3 text-sm text-gray-600">
          {`신규 ${importReport.created} · 갱신 ${importReport.updated} · 전체 ${importReport.total}`}
        </p>
      )}
      {importError && <p className="mt-3 text-sm text-red-600">{importError}</p>}

      <p className="mt-4 text-sm font-medium text-gray-700">
        {`총 ${total}건 · 처리완료 ${done}건`}
      </p>

      {error && <p className="mt-2 text-sm text-red-600">{error}</p>}

      {advancedOpen && (
        <div className="mt-3 flex flex-wrap items-end gap-2 rounded-md border border-gray-200 bg-gray-50 p-3">
          {(
            [
              ['사이트명', siteInput, setSiteInput],
              ['게시판명', boardInput, setBoardInput],
              ['게시물 제목', titleInput, setTitleInput],
            ] as const
          ).map(([label, value, setter]) => (
            <label key={label} className="flex flex-col gap-1 text-xs text-gray-500">
              {label}
              <input
                type="text"
                aria-label={label}
                value={value}
                onChange={(e) => setter(e.target.value)}
                onKeyDown={(e) => {
                  if (e.key === 'Enter') {
                    handleSearch()
                  }
                }}
                className="w-48 rounded-md border border-gray-300 px-2 py-1.5 text-sm"
              />
            </label>
          ))}
          <span className="pb-2 text-xs text-gray-400">세 조건은 모두 만족하는 건만 찾습니다(AND)</span>
        </div>
      )}

      {/* 일괄 등록: 체크한 건들에 같은 값을 한 번에 넣는다. 삭제도 여기서 한다. */}
      <div className="mt-3 flex flex-wrap items-center gap-2 rounded-md border border-gray-200 bg-gray-50 p-3">
        <span className="text-sm font-medium text-gray-700">선택 {selected.length}건</span>
        <select
          aria-label="일괄 처리상태"
          value={bulkStatus}
          onChange={(e) => setBulkStatus(e.target.value)}
          className="rounded-md border border-gray-300 px-2 py-1.5 text-sm"
        >
          <option value="">처리상태 그대로</option>
          <option value="처리완료">처리완료</option>
          <option value="미처리">미처리</option>
        </select>
        <select
          aria-label="일괄 공공누리 유형"
          value={bulkKoglType}
          onChange={(e) => setBulkKoglType(e.target.value)}
          className="rounded-md border border-gray-300 px-2 py-1.5 text-sm"
        >
          <option value="">공공누리 유형 그대로</option>
          {[...koglProcess.first, ...koglProcess.rest].map((option) => (
            <option key={option.code} value={option.code}>{option.label}</option>
          ))}
        </select>
        <input
          type="text"
          aria-label="일괄 최종의견"
          value={bulkOpinion}
          onChange={(e) => setBulkOpinion(e.target.value)}
          placeholder="최종의견(비우면 유지)"
          className="w-56 rounded-md border border-gray-300 px-2 py-1.5 text-sm"
        />
        <button
          type="button"
          disabled={selected.length === 0 || bulkBusy}
          onClick={() => void handleBulkApply()}
          className="rounded-md bg-blue-600 px-3 py-1.5 text-sm text-white disabled:bg-gray-300"
        >
          일괄 등록
        </button>
        <button
          type="button"
          disabled={selected.length === 0 || bulkBusy}
          onClick={() => void handleBulkDelete()}
          className="rounded-md border border-gray-300 px-3 py-1.5 text-sm text-gray-700 hover:bg-gray-50 disabled:text-gray-300"
        >
          선택 삭제
        </button>
        {bulkMessage && <span className="text-sm text-gray-600">{bulkMessage}</span>}
      </div>

      <div className="mt-3 overflow-x-auto rounded-lg border border-gray-200">
        <table className="w-full min-w-[1400px] text-left text-sm">
          <thead>
            <tr className="border-b border-gray-200 bg-gray-50 text-xs text-gray-500">
              <th className="w-8 px-3 py-2 font-medium">
                <input
                  type="checkbox"
                  aria-label="전체 선택"
                  checked={allChecked}
                  onChange={toggleAll}
                />
              </th>
              <th className="whitespace-nowrap px-3 py-2 font-medium">No</th>
              <th className="whitespace-nowrap px-3 py-2 font-medium">사이트/게시판</th>
              <th className="px-3 py-2 font-medium">게시물 경로/제목</th>
              <th className="whitespace-nowrap px-3 py-2 font-medium">제작/공표일</th>
              <th className="whitespace-nowrap px-3 py-2 font-medium">기존유형/계약서</th>
              <th className="whitespace-nowrap px-3 py-2 font-medium">권리확인구분</th>
              <th className="whitespace-nowrap px-3 py-2 font-medium">권리확인 공공누리유형</th>
              <th className="whitespace-nowrap px-3 py-2 font-medium">계약여부</th>
              <th className="whitespace-nowrap px-3 py-2 font-medium">처리 공공누리유형</th>
              <th className="px-3 py-2 font-medium">최종의견</th>
              <th className="whitespace-nowrap px-3 py-2 font-medium">처리상태</th>
              <th className="whitespace-nowrap px-3 py-2 font-medium">처리일시</th>
              <th className="whitespace-nowrap px-3 py-2 font-medium">관리</th>
            </tr>
          </thead>
          <tbody>
            {loading ? (
              <tr>
                <td colSpan={14} className="py-8 text-center text-sm text-gray-400">
                  불러오는 중…
                </td>
              </tr>
            ) : items.length === 0 ? (
              <tr>
                <td colSpan={14} className="py-10 text-center text-sm text-gray-300">
                  업로드된 권리처리 자료가 없습니다. 엑셀 업로드로 시작하세요.
                </td>
              </tr>
            ) : (
              items.map((item) => (
                <tr key={item.id} className="border-b border-gray-100 align-top last:border-b-0">
                  <td className="px-3 py-2">
                    <input
                      type="checkbox"
                      aria-label={`${item.seq}번 선택`}
                      checked={selected.includes(item.id)}
                      onChange={() => toggleOne(item.id)}
                    />
                  </td>
                  <td className="px-3 py-2 text-gray-500">{item.seq}</td>
                  <td className="px-3 py-2">
                    <div className="text-gray-900">{item.siteName ?? '-'}</div>
                    <div className="text-xs text-gray-400">{item.boardName ?? '-'}</div>
                  </td>
                  <td className="px-3 py-2">
                    <div className="flex items-center gap-1 text-gray-900">
                      <span>{item.postTitle ?? '-'}</span>
                      {item.url && (
                        <a
                          href={item.url}
                          target="_blank"
                          rel="noreferrer"
                          title="원문 보기"
                          aria-label="원문 보기"
                          className="text-blue-500 hover:underline"
                        >
                          🔗
                        </a>
                      )}
                    </div>
                    <div className="text-xs text-gray-400">{item.hasAttachment ?? '-'}</div>
                  </td>
                  <td className="px-3 py-2 text-gray-600">
                    <div>{item.producedDate ?? '-'}</div>
                    <div className="text-xs text-gray-400">{item.publishedDate ?? '-'}</div>
                  </td>
                  <td className="px-3 py-2 text-gray-600">
                    <div>{item.priorKoglType ?? '-'}</div>
                    <div className="text-xs text-gray-400">{item.contractDocs ?? '-'}</div>
                  </td>
                  <td className="px-3 py-2 text-gray-600">
                    <div>
                      {item.reviewMajor ?? '-'}
                      {item.reviewMinor ? ` > ${item.reviewMinor}` : ''}
                    </div>
                    <div className="text-xs text-gray-400">{item.reviewResult ?? ''}</div>
                  </td>
                  <td className="px-3 py-2 text-gray-600">
                    <div>{item.reviewKoglType ?? '-'}</div>
                    <div className="text-xs text-gray-400">{item.reviewAiType === 'Y' ? 'AI' : ''}</div>
                  </td>
                  <td className="px-3 py-2 text-gray-600">{item.contractDocs ?? '-'}</td>
                  <td className="px-3 py-2 text-gray-600">
                    <div>{item.judgedKoglType ?? '-'}</div>
                    <div className="text-xs text-gray-400">{item.judgedAiType === 'Y' ? 'AI' : ''}</div>
                  </td>
                  <td className="px-3 py-2 text-gray-600">{truncate(item.finalOpinion, 60)}</td>
                  <td className="px-3 py-2">
                    <ProcessStatusBadge value={item.processStatus} />
                  </td>
                  <td className="px-3 py-2 text-gray-600">
                    {item.processStatus === doneStatusCode ? formatDate(item.processedAt) : '-'}
                  </td>
                  <td className="px-3 py-2">
                    <div className="flex gap-2">
                      {item.processStatus === doneStatusCode ? (
                        <>
                          <button
                            type="button"
                            onClick={() => setMode(item.id)}
                            className="rounded-md border border-gray-200 px-2 py-0.5 text-xs text-gray-600 hover:bg-gray-50"
                          >
                            수정
                          </button>
                        </>
                      ) : (
                        <button
                          type="button"
                          onClick={() => setMode(item.id)}
                          className="rounded-md border border-emerald-300 bg-emerald-50 px-2 py-0.5 text-xs text-emerald-700 hover:bg-emerald-100"
                        >
                          처리등록
                        </button>
                      )}
                    </div>
                  </td>
                </tr>
              ))
            )}
          </tbody>
        </table>
      </div>

      {items.length > 0 && (
        <div className="mt-3 flex items-center justify-center gap-3 text-sm text-gray-600">
          <button
            type="button"
            disabled={page <= 0}
            onClick={() => setPage((p) => Math.max(0, p - 1))}
            className="rounded-md border border-gray-300 px-2 py-1 disabled:opacity-40"
          >
            ‹
          </button>
          <span>{`${page + 1} / ${totalPages} Page`}</span>
          <button
            type="button"
            disabled={page + 1 >= totalPages}
            onClick={() => setPage((p) => p + 1)}
            className="rounded-md border border-gray-300 px-2 py-1 disabled:opacity-40"
          >
            ›
          </button>
        </div>
      )}
    </div>
  )
}

/** 권리처리 상세([처리등록]/[수정]) 화면. */
function ProcessDetail({
  org,
  itemId,
  onBack,
  onSaved,
}: {
  org: Org
  itemId: number
  onBack: () => void
  onSaved: () => void
}) {
  const contractDocs = useCodes('CONTRACT_DOC')
  const etcDoc = etcDocOf(contractDocs)
  const statuses = useCodes('PROCESS_STATUS')
  // 권리처리 판정에는 '보류'가 없다 - 변호사 판정과 목록이 다르다.
  const kogl = useSplitByFirstRow('KOGL_TYPE', SCOPE_PROCESS)

  const [item, setItem] = useState<ProcessItem | null>(null)
  const [loading, setLoading] = useState(true)
  const [loadError, setLoadError] = useState<string | null>(null)

  const [contractSelected, setContractSelected] = useState<Set<string>>(new Set())
  const [contractEtc, setContractEtc] = useState('')
  const [judgedKoglType, setJudgedKoglType] = useState('')
  const [judgedAiType, setJudgedAiType] = useState(false)
  const [finalOpinion, setFinalOpinion] = useState('')
  const [judgmentBasis, setJudgmentBasis] = useState('')
  const [processStatus, setProcessStatus] = useState('')

  const [busy, setBusy] = useState(false)
  const [saveError, setSaveError] = useState<string | null>(null)

  useEffect(() => {
    let cancelled = false
    setLoading(true)
    setLoadError(null)

    getProcessItem(org.id, itemId)
      .then((data) => {
        if (cancelled) {
          return
        }
        setItem(data)
        const { selected, etcText } = parseContractDocs(data.contractDocs, contractDocs)
        setContractSelected(selected)
        setContractEtc(etcText)
        setJudgedKoglType(data.judgedKoglType ?? '')
        setJudgedAiType(data.judgedAiType === 'Y')
        setFinalOpinion(data.finalOpinion ?? '')
        setJudgmentBasis(data.judgmentBasis ?? '')
        setProcessStatus(data.processStatus ?? '')
      })
      .catch((e: Error) => {
        if (!cancelled) {
          setLoadError(e.message)
        }
      })
      .finally(() => {
        if (!cancelled) {
          setLoading(false)
        }
      })

    return () => {
      cancelled = true
    }
  }, [org.id, itemId])

  function toggleContractOption(option: string) {
    setContractSelected((prev) => {
      const next = new Set(prev)
      if (next.has(option)) {
        next.delete(option)
      } else {
        next.add(option)
      }
      return next
    })
  }

  async function handleSave() {
    if (!processStatus) {
      return
    }
    setBusy(true)
    setSaveError(null)
    try {
      const body: ProcessingRequest = {
        contractDocs: serializeContractDocs(contractSelected, contractEtc, contractDocs),
        judgedKoglType: judgedKoglType || null,
        judgedAiType: judgedAiType ? 'Y' : null,
        finalOpinion: finalOpinion || null,
        judgmentBasis: judgmentBasis || null,
        processStatus,
      }
      await updateProcessing(org.id, itemId, body)
      onSaved()
    } catch (e) {
      setSaveError(e instanceof Error ? e.message : '저장에 실패했습니다.')
    } finally {
      setBusy(false)
    }
  }

  async function handleDelete() {
    if (!item) {
      return
    }
    if (!window.confirm(`"${item.postTitle ?? item.seq}" 게시물을 삭제할까요?`)) {
      return
    }
    setBusy(true)
    setSaveError(null)
    try {
      await deleteProcessItem(org.id, itemId)
      onSaved()
    } catch (e) {
      setSaveError(e instanceof Error ? e.message : '삭제에 실패했습니다.')
    } finally {
      setBusy(false)
    }
  }

  if (loading) {
    return <p className="p-4 py-10 text-center text-sm text-gray-400">불러오는 중…</p>
  }
  if (loadError || !item) {
    return <p className="p-4 py-10 text-center text-sm text-red-600">{loadError ?? '게시물을 찾을 수 없습니다.'}</p>
  }

  return (
    <div className="p-4">
      <section className="rounded-lg border border-gray-200 p-4">
        <SectionHeader title="기본정보" />

        <div className="mt-3 overflow-hidden rounded border border-gray-300 divide-y divide-gray-200">
          <InfoRow label="기관명">
            <ReadonlyBox value={org.orgName} />
          </InfoRow>
          <InfoRowPair
            leftLabel="사이트명"
            left={<ReadonlyBox value={item.siteName} />}
            rightLabel="게시판명"
            right={<ReadonlyBox value={item.boardName} />}
          />
          <InfoRow label="게시물제목">
            <div className="flex w-full items-center gap-1.5 rounded border border-gray-200 bg-gray-100 px-3 py-1.5 text-sm text-gray-800">
              <span>{item.postTitle ?? '-'}</span>
              {item.url && (
                <a
                  href={item.url}
                  target="_blank"
                  rel="noreferrer"
                  title="원문 보기"
                  aria-label="원문 보기"
                  className="text-blue-500 hover:underline"
                >
                  🔗
                </a>
              )}
            </div>
          </InfoRow>
          <InfoRow label="기존 공공누리">
            <ReadonlyBox value={item.priorKoglType} />
          </InfoRow>
        </div>
      </section>

      <section className="mt-4 rounded-lg border border-gray-200 p-4">
        <SectionHeader title="권리확인" />

        <div className="mt-3 overflow-hidden rounded border border-gray-300 divide-y divide-gray-200">
          <InfoRow label="권리확인">
            <ReadonlyBox
              value={item.reviewMajor ? `${item.reviewMajor}${item.reviewMinor ? ` > ${item.reviewMinor}` : ''}` : null}
            />
          </InfoRow>
          <InfoRow label="권리확인 공공누리유형">
            <ReadonlyBox
              value={item.reviewKoglType ? `${item.reviewKoglType}${item.reviewAiType === 'Y' ? ' + AI' : ''}` : null}
            />
          </InfoRow>
          <InfoRow label="처리 구분">
            <ReadonlyBox value={item.reviewResult} />
          </InfoRow>
          <InfoRow label="의견">
            <ReadonlyBox value={item.reviewOpinion} />
          </InfoRow>
          <InfoRow label="비고">
            <ReadonlyBox value={item.reviewNote} />
          </InfoRow>
        </div>
      </section>

      <section className="mt-4 rounded-lg border border-gray-200 p-4">
        <SectionHeader title="권리처리" />

        <div className="mt-3 overflow-hidden rounded border border-gray-300 divide-y divide-gray-200">
          <InfoRow label="계약서 유무">
            <div className="flex flex-col gap-1.5">
              <div className="flex flex-wrap gap-x-4 gap-y-1">
                {/* '기타'는 자유 입력이 딸려 있어 뒤에 따로 그린다. */}
                {contractDocs
                  .filter((option) => option.attrs.freeText !== true)
                  .map((option) => (
                    <label key={option.code} className="flex items-center gap-1.5 text-sm text-gray-800">
                      <input
                        type="checkbox"
                        checked={contractSelected.has(option.code)}
                        onChange={() => toggleContractOption(option.code)}
                      />
                      {option.label}
                    </label>
                  ))}
                {etcDoc && (
                  <label className="flex items-center gap-1.5 text-sm text-gray-800">
                    <input
                      type="checkbox"
                      checked={contractSelected.has(etcDoc.code)}
                      onChange={() => toggleContractOption(etcDoc.code)}
                    />
                    {etcDoc.label}
                  </label>
                )}
              </div>
              {etcDoc && contractSelected.has(etcDoc.code) && (
                <input
                  type="text"
                  aria-label="기타 계약서 내용"
                  value={contractEtc}
                  onChange={(e) => setContractEtc(e.target.value)}
                  placeholder="기타 내용을 입력하세요"
                  className="w-72 rounded-md border border-gray-300 px-2 py-1.5 text-sm"
                />
              )}
            </div>
          </InfoRow>

          <InfoRow label="공공누리 유형">
            <div className="flex flex-col gap-1.5">
              <div className="flex flex-wrap gap-x-4 gap-y-1">
                {kogl.first.map((option) => (
                  <label key={option.code} className="flex items-center gap-1.5 text-sm text-gray-800">
                    <input
                      type="radio"
                      name="judged-kogl-type"
                      checked={judgedKoglType === option.code}
                      onChange={() => setJudgedKoglType(option.code)}
                    />
                    {option.label}
                  </label>
                ))}
              </div>
              <div className="flex flex-wrap items-center gap-x-4 gap-y-1">
                {kogl.rest.map((option) => (
                  <label key={option.code} className="flex items-center gap-1.5 text-sm text-gray-800">
                    <input
                      type="radio"
                      name="judged-kogl-type"
                      checked={judgedKoglType === option.code}
                      onChange={() => setJudgedKoglType(option.code)}
                    />
                    {option.label}
                  </label>
                ))}
                <label className="ml-2 flex items-center gap-1.5 text-sm text-gray-800">
                  <input
                    type="checkbox"
                    checked={judgedAiType}
                    onChange={(e) => setJudgedAiType(e.target.checked)}
                  />
                  AI유형
                </label>
              </div>
            </div>
          </InfoRow>

          <div className="grid grid-cols-[150px_1fr]">
            <LabelCell>권리처리 결과</LabelCell>
            <div className="flex flex-col divide-y divide-gray-200">
              <div className="grid grid-cols-[110px_1fr]">
                <div className="flex items-center bg-gray-50 px-3 py-2 text-xs font-medium text-gray-600">
                  최종의견
                </div>
                <div className="flex flex-col gap-1 px-3 py-1.5">
                  <span className="text-[11px] text-gray-400">
                    작성안내) 전부 양도 체결, 일부 양도 체결, 초상 이용 동의, 공공누리 동의 등 처리한 사유 기입
                  </span>
                  <textarea
                    id="process-final-opinion"
                    aria-label="최종의견"
                    rows={3}
                    value={finalOpinion}
                    onChange={(e) => setFinalOpinion(e.target.value)}
                    className="w-full rounded border border-gray-300 px-2 py-1.5 text-sm"
                  />
                </div>
              </div>
              <div className="grid grid-cols-[110px_1fr]">
                <div className="flex items-center bg-gray-50 px-3 py-2 text-xs font-medium text-gray-600">
                  판단근거
                </div>
                <div className="flex flex-col gap-1 px-3 py-1.5">
                  <span className="text-[11px] text-gray-400">작성안내) 계약서 명칭, 해당 문구 기입</span>
                  <textarea
                    id="process-judgment-basis"
                    aria-label="판단근거"
                    rows={3}
                    value={judgmentBasis}
                    onChange={(e) => setJudgmentBasis(e.target.value)}
                    className="w-full rounded border border-gray-300 px-2 py-1.5 text-sm"
                  />
                </div>
              </div>
            </div>
          </div>

          <InfoRow label="권리처리상태" required>
            <select
              id="process-status"
              aria-label="*권리처리상태"
              value={processStatus}
              onChange={(e) => setProcessStatus(e.target.value)}
              className="w-40 rounded-md border border-gray-300 px-2 py-1.5 text-sm"
            >
              <option value="">선택</option>
              {statuses.map((option) => (
                <option key={option.code} value={option.code}>
                  {option.label}
                </option>
              ))}
            </select>
          </InfoRow>
        </div>

        {saveError && <p className="mt-3 text-sm text-red-600">{saveError}</p>}

        <div className="mt-4 flex justify-end gap-2">
          <button
            type="button"
            disabled={busy}
            onClick={onBack}
            className="rounded-md bg-gray-600 px-3 py-1.5 text-sm font-semibold text-white hover:bg-gray-700 disabled:opacity-50"
          >
            목록
          </button>
          <button
            type="button"
            disabled={busy}
            onClick={() => void handleDelete()}
            className="rounded-md border border-red-300 px-3 py-1.5 text-sm font-semibold text-red-600 hover:bg-red-50 disabled:opacity-50"
          >
            삭제
          </button>
          <button
            type="button"
            disabled={busy || !processStatus}
            onClick={() => void handleSave()}
            className="rounded-md bg-blue-700 px-4 py-1.5 text-sm font-semibold text-white hover:bg-blue-800 disabled:cursor-not-allowed disabled:bg-gray-300"
          >
            수정
          </button>
        </div>
      </section>
    </div>
  )
}
