<script setup lang="ts">
/**
 * Props-driven `datatable` composition.
 *
 * Renders a paginated, searchable, sortable table from `headers` + a
 * row source determined in priority order:
 *
 *   1. `options.searchRoute` — remote fetch (stubbed; future HTTP).
 *   2. `listKey` — read sub-array from `item[listKey]` (nested).
 *   3. `listData` — inline static rows.
 *   4. Otherwise: empty.
 *
 * Sorting, search, and pagination all run client-side over the
 * resolved rows. Column rendering branches on `header.type`.
 *
 * Self-contained — directly mountable in any Vue app:
 *
 *   <DataTableCard
 *     :headers="[{ key: 'name', sortable: true }]"
 *     :list-data="rows"
 *     @action="onRowAction"
 *   />
 */
import { computed, ref, watch, toRef } from "vue"
import type {
  CreateConfig,
  DeleteConfig,
  UpdateConfig,
} from "@/definitions/shared/crud"
import type {
  DataTableColumn,
  DataTableOptions,
  ExportConfig,
  TableAction,
} from "@/definitions/cards/DataTableCard"
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { useDataTableData } from "./useDataTableData"

export interface DataTableCardProps {
  /** Title rendered above the table. Falls back to the more generic `title`. */
  tableTitle?: string
  title?: string
  subtitle?: string
  showCard?: boolean
  classList?: string[] | string
  /** Column definitions. */
  headers?: DataTableColumn[]
  /** Behaviour and toolbar configuration. */
  options?: DataTableOptions
  /** Sub-array path on `item` when rendering nested tables. */
  listKey?: string
  /** Static (non-paginated) row source. */
  listData?: unknown[]
  /** Per-row action buttons (also merged with `options.additionalActions`). */
  actions?: TableAction[]
  /** Enable row selection. */
  selectable?: boolean
  /** Bulk action buttons. */
  bulkActions?: TableAction[]
  /** Empty-state message. */
  emptyMessage?: string
  /** Page-level data model. Used as the row source when `listKey` is set. */
  item?: Record<string, unknown>
  /** Create-modal configuration. */
  createConfig?: CreateConfig
  /** Inline update configuration. */
  updateConfig?: UpdateConfig
  /** Delete configuration. */
  deleteConfig?: DeleteConfig
}

const props = withDefaults(defineProps<DataTableCardProps>(), {
  headers: () => [],
  selectable: false,
  showCard: true,
  item: () => ({}),
})

const emit = defineEmits<{
  (e: "action", payload: { action: TableAction; row: Record<string, unknown> }): void
  (e: "export", payload: { export: ExportConfig }): void
}>()

const sortState = ref<{ key: string; direction: "asc" | "desc" } | null>(
  props.options?.defaultSortBy
    ? {
        key: props.options.defaultSortBy,
        direction: props.options.defaultSortDirection ?? "asc",
      }
    : null,
)

const searchQuery = ref("")
const currentPage = ref(1)

const perPage = computed<number>(() => {
  const opts = props.options
  return (
    opts?.perPage ??
    opts?.defaultItemsPerPage ??
    25
  )
})

const paginated = computed<boolean>(() => props.options?.paginated === true)

const visibleHeaders = computed<DataTableColumn[]>(() =>
  (props.headers ?? []).filter((h) => h.visible !== false),
)

const emptyMessageText = computed<string>(() => {
  return (
    props.emptyMessage ??
    props.options?.emptyText ??
    "No results"
  )
})

const exports = computed<ExportConfig[]>(
  () => props.options?.exports ?? [],
)

const tableActions = computed<TableAction[]>(() => {
  const base = props.actions ?? []
  const additional = props.options?.additionalActions ?? []
  return [...base, ...additional]
})

const showSearch = computed<boolean>(() => {
  const sr = props.options?.searchRoute
  if (sr && sr.hide) {
    return false
  }
  // Always show search when there is any data source (client-side filter).
  return true
})

/** Underlying row source — composable that can be mocked in tests. */
const { rows: sourceRows } = useDataTableData({
  listKey: toRef(props, "listKey"),
  listData: toRef(props, "listData"),
  options: toRef(props, "options"),
  item: toRef(props, "item"),
})

/** Read a dot-notation path on a row, returning unknown. */
function readPath(row: Record<string, unknown>, path: string): unknown {
  if (!path) {
    return undefined
  }
  const segments = path.split(".")
  let value: unknown = row
  for (const seg of segments) {
    if (value == null || typeof value !== "object") {
      return undefined
    }
    value = (value as Record<string, unknown>)[seg]
  }
  return value
}

function toComparable(value: unknown): number | string {
  if (value == null) {
    return ""
  }
  if (typeof value === "number") {
    return value
  }
  if (typeof value === "boolean") {
    return value ? 1 : 0
  }
  if (value instanceof Date) {
    return value.getTime()
  }
  return String(value).toLowerCase()
}

const filteredRows = computed<Record<string, unknown>[]>(() => {
  const query = searchQuery.value.trim().toLowerCase()
  if (!query) {
    return sourceRows.value
  }
  return sourceRows.value.filter((row) => {
    return visibleHeaders.value.some((h) => {
      const v = readPath(row, h.key)
      if (v == null) {
        return false
      }
      return String(v).toLowerCase().includes(query)
    })
  })
})

const sortedRows = computed<Record<string, unknown>[]>(() => {
  const state = sortState.value
  if (!state) {
    return filteredRows.value
  }
  const dir = state.direction === "asc" ? 1 : -1
  return [...filteredRows.value].sort((a, b) => {
    const av = toComparable(readPath(a, state.key))
    const bv = toComparable(readPath(b, state.key))
    if (av < bv) {
      return -1 * dir
    }
    if (av > bv) {
      return 1 * dir
    }
    return 0
  })
})

const pageCount = computed<number>(() => {
  if (!paginated.value) {
    return 1
  }
  return Math.max(1, Math.ceil(sortedRows.value.length / perPage.value))
})

const pagedRows = computed<Record<string, unknown>[]>(() => {
  if (!paginated.value) {
    return sortedRows.value
  }
  const start = (currentPage.value - 1) * perPage.value
  return sortedRows.value.slice(start, start + perPage.value)
})

// Reset pagination if filtering shrinks the result below the current page.
watch([filteredRows, perPage], () => {
  if (currentPage.value > pageCount.value) {
    currentPage.value = 1
  }
})

function toggleSort(header: DataTableColumn): void {
  if (!header.sortable) {
    return
  }
  const state = sortState.value
  if (!state || state.key !== header.key) {
    sortState.value = { key: header.key, direction: "asc" }
    return
  }
  if (state.direction === "asc") {
    sortState.value = { key: header.key, direction: "desc" }
    return
  }
  sortState.value = null
}

function sortIndicator(header: DataTableColumn): string {
  const state = sortState.value
  if (!state || state.key !== header.key) {
    return ""
  }
  return state.direction === "asc" ? "↑" : "↓"
}

function alignClass(header: DataTableColumn): string {
  const a = header.align
  if (a === "center") {
    return "text-center"
  }
  if (a === "end" || a === "right") {
    return "text-right"
  }
  return "text-left"
}

function previousPage(): void {
  if (currentPage.value > 1) {
    currentPage.value -= 1
  }
}

function nextPage(): void {
  if (currentPage.value < pageCount.value) {
    currentPage.value += 1
  }
}

function formatTimestamp(value: unknown): string {
  if (value == null) {
    return ""
  }
  const date =
    value instanceof Date
      ? value
      : new Date(typeof value === "number" ? value : String(value))
  if (Number.isNaN(date.getTime())) {
    return String(value)
  }
  return new Intl.DateTimeFormat(undefined, {
    year: "numeric",
    month: "short",
    day: "numeric",
    hour: "numeric",
    minute: "numeric",
  }).format(date)
}

function statusColor(value: unknown): string {
  const s = String(value ?? "").toLowerCase()
  if (s === "success" || s === "active" || s === "ok" || s === "paid") {
    return "bg-green-500"
  }
  if (s === "warning" || s === "pending") {
    return "bg-yellow-500"
  }
  if (s === "danger" || s === "error" || s === "failed" || s === "unpaid") {
    return "bg-red-500"
  }
  if (s === "info") {
    return "bg-blue-500"
  }
  return "bg-gray-400"
}

function onAction(action: TableAction, row: Record<string, unknown>): void {
  emit("action", { action, row })
}

function onExport(config: ExportConfig): void {
  emit("export", { export: config })
}
</script>

<template>
  <section
    :class="[
      'gxp-datatable-card flex flex-col gap-3',
      showCard !== false
        ? 'rounded-lg border bg-card text-card-foreground shadow-sm p-4'
        : '',
    ]"
    data-testid="datatable-card"
  >
    <header
      v-if="tableTitle || title || subtitle"
      class="flex flex-col gap-0.5"
    >
      <h3
        v-if="tableTitle || title"
        class="text-base font-semibold"
      >
        {{ tableTitle ?? title }}
      </h3>
      <p
        v-if="subtitle"
        class="text-sm text-muted-foreground"
      >
        {{ subtitle }}
      </p>
    </header>

    <div
      v-if="exports.length > 0 || showSearch"
      class="flex flex-wrap items-center justify-between gap-2"
    >
      <div
        v-if="exports.length > 0"
        class="flex items-center gap-2"
        data-testid="datatable-exports"
      >
        <Button
          v-for="(ex, i) in exports"
          :key="i"
          variant="outline"
          size="sm"
          type="button"
          @click="onExport(ex)"
        >
          {{ ex.label ?? "Export" }}
        </Button>
      </div>

      <div v-if="showSearch" class="ml-auto w-full max-w-xs">
        <Input
          v-model="searchQuery"
          type="search"
          placeholder="Search..."
          data-testid="datatable-search"
        />
      </div>
    </div>

    <Table>
      <TableHeader>
        <TableRow>
          <TableHead
            v-for="h in visibleHeaders"
            :key="h.key"
            :class="alignClass(h)"
            :style="h.width ? { width: h.width } : undefined"
          >
            <button
              v-if="h.sortable"
              type="button"
              class="inline-flex items-center gap-1 hover:text-foreground"
              :data-testid="`sort-${h.key}`"
              @click="toggleSort(h)"
            >
              <span>{{ h.title ?? h.key }}</span>
              <span class="text-xs">{{ sortIndicator(h) }}</span>
            </button>
            <span v-else>{{ h.title ?? h.key }}</span>
          </TableHead>
          <TableHead
            v-if="tableActions.length > 0"
            class="text-right"
          >
            Actions
          </TableHead>
        </TableRow>
      </TableHeader>
      <TableBody>
        <template v-if="pagedRows.length > 0">
          <TableRow
            v-for="(row, ri) in pagedRows"
            :key="ri"
            :data-testid="`datatable-row-${ri}`"
          >
            <TableCell
              v-for="h in visibleHeaders"
              :key="h.key"
              :class="alignClass(h)"
            >
              <template v-if="h.type === 'timestamp' || h.type === 'laravel-timestamp'">
                <span data-testid="cell-timestamp">{{ formatTimestamp(readPath(row, h.key)) }}</span>
              </template>
              <template v-else-if="h.type === 'boolean'">
                <span
                  data-testid="cell-boolean"
                  :class="readPath(row, h.key) ? 'text-green-600' : 'text-red-600'"
                  :aria-label="readPath(row, h.key) ? 'true' : 'false'"
                >
                  {{ readPath(row, h.key) ? "✓" : "✗" }}
                </span>
              </template>
              <template v-else-if="h.type === 'image'">
                <img
                  data-testid="cell-image"
                  :src="String(readPath(row, h.key) ?? '')"
                  :alt="h.title ?? h.key"
                  class="h-8 w-8 rounded object-cover"
                >
              </template>
              <template v-else-if="h.type === 'status_circle'">
                <span class="inline-flex items-center gap-2">
                  <span
                    data-testid="cell-status-circle"
                    :class="['inline-block h-2.5 w-2.5 rounded-full', statusColor(readPath(row, h.key))]"
                  />
                  <span>{{ readPath(row, h.key) }}</span>
                </span>
              </template>
              <template v-else>
                <span>
                  <template v-if="h.prepend">{{ h.prepend }}</template>
                  {{ readPath(row, h.key) ?? "" }}
                  <template v-if="h.append">{{ h.append }}</template>
                </span>
              </template>
            </TableCell>
            <TableCell
              v-if="tableActions.length > 0"
              class="text-right"
            >
              <div class="inline-flex items-center justify-end gap-1">
                <Button
                  v-for="(action, ai) in tableActions"
                  :key="ai"
                  variant="ghost"
                  size="sm"
                  type="button"
                  :data-testid="`datatable-action-${action.slug ?? ai}`"
                  @click="onAction(action, row)"
                >
                  {{ action.label ?? action.tooltip ?? action.slug ?? "Action" }}
                </Button>
              </div>
            </TableCell>
          </TableRow>
        </template>
        <template v-else>
          <TableRow>
            <TableCell
              :colspan="visibleHeaders.length + (tableActions.length > 0 ? 1 : 0)"
              class="text-center text-muted-foreground"
            >
              <span data-testid="datatable-empty">{{ emptyMessageText }}</span>
            </TableCell>
          </TableRow>
        </template>
      </TableBody>
    </Table>

    <footer
      v-if="paginated && sortedRows.length > 0"
      class="flex items-center justify-between gap-2"
      data-testid="datatable-pagination"
    >
      <span class="text-sm text-muted-foreground">
        Page {{ currentPage }} of {{ pageCount }}
      </span>
      <div class="flex items-center gap-2">
        <Button
          variant="outline"
          size="sm"
          type="button"
          :disabled="currentPage <= 1"
          data-testid="datatable-prev"
          @click="previousPage"
        >
          Previous
        </Button>
        <Button
          variant="outline"
          size="sm"
          type="button"
          :disabled="currentPage >= pageCount"
          data-testid="datatable-next"
          @click="nextPage"
        >
          Next
        </Button>
      </div>
    </footer>
  </section>
</template>
