<script setup lang="ts">
import { computed, ref } from "vue";
import { storeToRefs } from "pinia";
import type { StationSnapshot } from "../contractsInfrastructure";
import type { PlayerFleetSnapshot } from "../contractsPlayerFaction";
import type { ShipSnapshot } from "../contractsShips";
import { getShipBehaviorLabel } from "../shipAutomationPresentation";
import { useGmStore } from "../ui/stores/gmStore";
import { usePlayerFactionStore } from "../ui/stores/playerFactionStore";
import { useViewerSceneStore } from "../ui/stores/viewerScene";
import { useViewerSelectionStore, type ViewerSelectionSummary } from "../ui/stores/viewerSelection";
import type { Selectable } from "../viewerTypes";

type BrowserTab = "visible" | "owned";
type BrowserSortKey = "entity" | "location" | "ai" | "hp";
type BrowserRowKind = "system" | "station" | "fleet" | "ship";

interface BrowserRow {
  key: string;
  kind: BrowserRowKind;
  kindLabel: string;
  name: string;
  ident: string;
  location: string;
  aiStates: string[];
  hpLabel: string;
  hpValue: number;
  selection?: ViewerSelectionSummary;
  focusSelection?: Selectable;
  focusMode?: "follow" | "tactical";
  children: BrowserRow[];
}

interface BrowserDisplayRow extends BrowserRow {
  depth: number;
}

const emit = defineEmits<{
  focus: [selection: Selectable, cameraMode?: "follow" | "tactical"];
}>();

const gmStore = useGmStore();
const playerStore = usePlayerFactionStore();
const selectionStore = useViewerSelectionStore();
const sceneStore = useViewerSceneStore();

const { selectedEntityId, selectedEntityKind } = storeToRefs(selectionStore);
const { playerFaction } = storeToRefs(playerStore);
const { activeSystemId, povLevel } = storeToRefs(sceneStore);

const activeTab = ref<BrowserTab>("owned");
const sortKey = ref<BrowserSortKey>("entity");
const sortDirection = ref<"asc" | "desc">("asc");
const searchText = ref("");
const expandedRowKeys = ref<Record<string, boolean>>({});

const systemById = computed(() => new Map(gmStore.systems.map((system) => [system.id, system])));
const stationById = computed(() => new Map(gmStore.stations.map((station) => [station.id, station])));
const playerFleetByShipId = computed(() => {
  const mapping = new Map<string, PlayerFleetSnapshot>();
  for (const fleet of playerFaction.value?.fleets ?? []) {
    for (const assetId of fleet.assetIds) {
      mapping.set(assetId, fleet);
    }
  }
  return mapping;
});

function normalize(text: string) {
  return text.trim().toLowerCase();
}

function titleCase(value: string | null | undefined) {
  if (!value) {
    return "Unknown";
  }

  return value
    .replace(/([a-z])([A-Z])/g, "$1 $2")
    .replace(/[-_]+/g, " ")
    .replace(/\s+/g, " ")
    .trim()
    .replace(/\b\w/g, (part) => part.toUpperCase());
}

function compactLabel(value: string | null | undefined, fallback: string) {
  if (!value) {
    return fallback;
  }

  const words = titleCase(value).split(" ");
  if (words.length === 1) {
    return words[0].slice(0, 4).toUpperCase();
  }
  return words
    .slice(0, 2)
    .map((word) => word.slice(0, 3).toUpperCase())
    .join("-");
}

function shortId(value: string) {
  if (value.length <= 8) {
    return value.toUpperCase();
  }
  return `${value.slice(0, 4).toUpperCase()}-${value.slice(-4).toUpperCase()}`;
}

function uniqueTokens(tokens: string[]) {
  return tokens.filter((token, index) => token.length > 0 && tokens.indexOf(token) === index);
}

function formatShipLocation(ship: ShipSnapshot) {
  const dockedStation = ship.dockedStationId ? stationById.value.get(ship.dockedStationId) : undefined;
  if (dockedStation) {
    return `Docked ${dockedStation.label}`;
  }

  if (ship.spatialState.transit?.destinationNodeId) {
    return `Transit ${ship.systemId}`;
  }

  if (ship.celestialId) {
    return `Orbit ${titleCase(ship.celestialId)}`;
  }

  const system = systemById.value.get(ship.systemId);
  return system?.label ?? ship.systemId;
}

function formatStationLocation(station: StationSnapshot) {
  const system = systemById.value.get(station.systemId);
  if (station.celestialId) {
    return `${system?.label ?? station.systemId} · ${titleCase(station.celestialId)}`;
  }

  return system?.label ?? station.systemId;
}

function shipAiStates(ship: ShipSnapshot) {
  const travelToken = ship.spatialState.transit ? "TRV" : "";
  const dockToken = ship.dockedStationId ? "DCK" : "";
  const behaviorToken = compactLabel(getShipBehaviorLabel(ship.defaultBehavior.kind), "AUTO");
  const planToken = ship.activePlan?.steps.length ? "PLAN" : "";
  const orderToken = ship.orderQueue.length > 0 ? "ORD" : "";
  const commandToken = ship.commanderId ? "CMD" : "";

  return uniqueTokens([behaviorToken, orderToken, planToken, travelToken, dockToken, commandToken]).slice(0, 5);
}

function stationAiStates(station: StationSnapshot) {
  return uniqueTokens([
    station.currentProcesses.length > 0 ? "PROC" : "",
    station.dockedShips > 0 ? "DCK" : "",
    station.commanderId ? "CMD" : "",
  ]);
}

function fleetAiStates(fleet: PlayerFleetSnapshot) {
  return uniqueTokens([
    compactLabel(fleet.status, "STAT"),
    compactLabel(fleet.role, "ROLE"),
    fleet.commanderId ? "CMD" : "",
  ]);
}

function systemAiStates(systemId: string) {
  const stations = gmStore.stations.filter((station) => station.systemId === systemId).length;
  const ships = gmStore.ships.filter((ship) => ship.systemId === systemId).length;
  return uniqueTokens([stations > 0 ? `ST${stations}` : "", ships > 0 ? `SH${ships}` : ""]);
}

function buildShipRow(ship: ShipSnapshot): BrowserRow {
  return {
    key: `ship-${ship.id}`,
    kind: "ship",
    kindLabel: "SH",
    name: ship.name,
    ident: `${titleCase(ship.type)} · ${shortId(ship.id)}`,
    location: formatShipLocation(ship),
    aiStates: shipAiStates(ship),
    hpLabel: Math.round(ship.health).toString(),
    hpValue: ship.health,
    selection: { id: ship.id, kind: "ship", label: ship.name },
    focusSelection: { kind: "ship", id: ship.id },
    focusMode: "tactical",
    children: [],
  };
}

function buildStationRow(station: StationSnapshot, children: BrowserRow[]): BrowserRow {
  return {
    key: `station-${station.id}`,
    kind: "station",
    kindLabel: "ST",
    name: station.label,
    ident: `${titleCase(station.category)} · ${titleCase(station.objective)}`,
    location: formatStationLocation(station),
    aiStates: stationAiStates(station),
    hpLabel: "--",
    hpValue: -1,
    selection: { id: station.id, kind: "station", label: station.label },
    focusSelection: { kind: "station", id: station.id },
    focusMode: "tactical",
    children,
  };
}

function buildFleetRow(fleet: PlayerFleetSnapshot, children: BrowserRow[]): BrowserRow {
  const homeStation = fleet.homeStationId ? stationById.value.get(fleet.homeStationId) : undefined;
  const homeSystem = fleet.homeSystemId ? systemById.value.get(fleet.homeSystemId) : undefined;
  return {
    key: `fleet-${fleet.id}`,
    kind: "fleet",
    kindLabel: "FL",
    name: fleet.label,
    ident: `${titleCase(fleet.role)} · ${shortId(fleet.id)}`,
    location: homeStation ? `Home ${homeStation.label}` : (homeSystem?.label ?? "No home"),
    aiStates: fleetAiStates(fleet),
    hpLabel: `${children.length}`,
    hpValue: children.length,
    children,
  };
}

function buildSystemRow(systemId: string): BrowserRow | null {
  const system = systemById.value.get(systemId);
  if (!system) {
    return null;
  }

  return {
    key: `system-${system.id}`,
    kind: "system",
    kindLabel: "SY",
    name: system.label,
    ident: shortId(system.id),
    location: "Galaxy",
    aiStates: systemAiStates(system.id),
    hpLabel: "--",
    hpValue: -1,
    selection: { id: system.id, kind: "system", label: system.label },
    focusSelection: { kind: "system", id: system.id },
    focusMode: "tactical",
    children: [],
  };
}

function buildVisibleRows() {
  if (povLevel.value === "galaxy" || !activeSystemId.value) {
    return gmStore.systems
      .map((system) => buildSystemRow(system.id))
      .filter((row): row is BrowserRow => row != null);
  }

  const systemId = activeSystemId.value;
  const stations = gmStore.stations.filter((station) => station.systemId === systemId);
  const ships = gmStore.ships.filter((ship) => ship.systemId === systemId);
  const stationIds = new Set(stations.map((station) => station.id));
  const stationChildren = new Map<string, BrowserRow[]>();
  const fleetChildren = new Map<string, BrowserRow[]>();
  const independentShips: BrowserRow[] = [];

  for (const ship of ships) {
    const row = buildShipRow(ship);

    if (ship.dockedStationId && stationIds.has(ship.dockedStationId)) {
      const children = stationChildren.get(ship.dockedStationId) ?? [];
      children.push(row);
      stationChildren.set(ship.dockedStationId, children);
      continue;
    }

    const fleet = playerFleetByShipId.value.get(ship.id);
    if (fleet) {
      const children = fleetChildren.get(fleet.id) ?? [];
      children.push(row);
      fleetChildren.set(fleet.id, children);
      continue;
    }

    independentShips.push(row);
  }

  const stationRows = stations.map((station) => buildStationRow(station, stationChildren.get(station.id) ?? []));
  const fleetRows = (playerFaction.value?.fleets ?? [])
    .filter((fleet) => (fleetChildren.get(fleet.id)?.length ?? 0) > 0)
    .map((fleet) => buildFleetRow(fleet, fleetChildren.get(fleet.id) ?? []));

  return [...stationRows, ...fleetRows, ...independentShips];
}

function buildOwnedRows() {
  const player = playerFaction.value;
  if (!player) {
    return [];
  }

  const ownedShips = player.assetRegistry.shipIds
    .map((shipId) => gmStore.ships.find((ship) => ship.id === shipId))
    .filter((ship): ship is ShipSnapshot => ship != null);
  const ownedStations = player.assetRegistry.stationIds
    .map((stationId) => gmStore.stations.find((station) => station.id === stationId))
    .filter((station): station is StationSnapshot => station != null);
  const ownedFleetShipIds = new Set(player.fleets.flatMap((fleet) => fleet.assetIds));
  const ownedStationIds = new Set(ownedStations.map((station) => station.id));

  const stationChildren = new Map<string, BrowserRow[]>();
  for (const ship of ownedShips) {
    if (!ship.dockedStationId || !ownedStationIds.has(ship.dockedStationId) || ownedFleetShipIds.has(ship.id)) {
      continue;
    }
    const children = stationChildren.get(ship.dockedStationId) ?? [];
    children.push(buildShipRow(ship));
    stationChildren.set(ship.dockedStationId, children);
  }

  const stationRows = ownedStations.map((station) => buildStationRow(station, stationChildren.get(station.id) ?? []));
  const fleetRows = player.fleets.map((fleet) => buildFleetRow(
    fleet,
    fleet.assetIds
      .map((shipId) => ownedShips.find((ship) => ship.id === shipId))
      .filter((ship): ship is ShipSnapshot => ship != null)
      .map((ship) => buildShipRow(ship)),
  ));
  const independentShips = ownedShips
    .filter((ship) => !ownedFleetShipIds.has(ship.id) && (!ship.dockedStationId || !ownedStationIds.has(ship.dockedStationId)))
    .map((ship) => buildShipRow(ship));

  return [...stationRows, ...fleetRows, ...independentShips];
}

function getRowSortValue(row: BrowserRow, key: BrowserSortKey) {
  if (key === "hp") {
    return row.hpValue;
  }

  if (key === "location") {
    return row.location;
  }

  if (key === "ai") {
    return row.aiStates.join(" ");
  }

  return `${row.name} ${row.ident}`;
}

function sortRows(rows: BrowserRow[]): BrowserRow[] {
  const direction = sortDirection.value === "asc" ? 1 : -1;
  return [...rows]
    .sort((left, right) => {
      const leftValue = getRowSortValue(left, sortKey.value);
      const rightValue = getRowSortValue(right, sortKey.value);

      if (typeof leftValue === "number" && typeof rightValue === "number") {
        return (leftValue - rightValue) * direction;
      }

      return String(leftValue).localeCompare(String(rightValue)) * direction;
    })
    .map((row) => ({
      ...row,
      children: sortRows(row.children),
    }));
}

function rowMatches(row: BrowserRow, search: string) {
  if (!search) {
    return true;
  }

  const haystack = `${row.name} ${row.ident} ${row.location} ${row.aiStates.join(" ")} ${row.hpLabel}`.toLowerCase();
  return haystack.includes(search);
}

function isExpanded(row: BrowserRow) {
  if (row.children.length === 0) {
    return false;
  }

  return expandedRowKeys.value[row.key] ?? true;
}

function flattenRows(rows: BrowserRow[], search: string, depth = 0, forceExpand = false): BrowserDisplayRow[] {
  const flattened: BrowserDisplayRow[] = [];

  for (const row of rows) {
    const descendantMatches = flattenRows(row.children, search, depth + 1, forceExpand);
    const matches = rowMatches(row, search);
    if (!matches && descendantMatches.length === 0) {
      continue;
    }

    flattened.push({
      ...row,
      depth,
    });

    if (row.children.length > 0 && (forceExpand || isExpanded(row))) {
      flattened.push(...descendantMatches);
    }
  }

  return flattened;
}

const rawRows = computed(() => {
  if (activeTab.value === "owned") {
    return buildOwnedRows();
  }
  return buildVisibleRows();
});

const displayRows = computed(() => {
  const search = normalize(searchText.value);
  const sortedRows = sortRows(rawRows.value);
  return flattenRows(sortedRows, search, 0, search.length > 0);
});

function toggleSort(nextKey: BrowserSortKey) {
  if (sortKey.value === nextKey) {
    sortDirection.value = sortDirection.value === "asc" ? "desc" : "asc";
    return;
  }

  sortKey.value = nextKey;
  sortDirection.value = "asc";
}

function toggleRow(row: BrowserDisplayRow) {
  if (row.children.length === 0) {
    return;
  }

  expandedRowKeys.value[row.key] = !isExpanded(row);
}

function selectItem(row: BrowserDisplayRow) {
  if (!row.selection) {
    return;
  }

  selectionStore.selectSelection(row.selection, "ui");
}

function focusItem(row: BrowserDisplayRow) {
  if (row.selection) {
    selectionStore.selectSelection(row.selection, "ui");
  }
  if (row.focusSelection) {
    emit("focus", row.focusSelection, row.focusMode);
  }
}

function isSelected(row: BrowserDisplayRow) {
  return !!row.selection
    && row.selection.id === selectedEntityId.value
    && row.selection.kind === selectedEntityKind.value;
}

function sortMarker(key: BrowserSortKey) {
  if (sortKey.value !== key) {
    return "";
  }

  return sortDirection.value === "asc" ? " ▲" : " ▼";
}
</script>

<template>
  <section class="entity-browser-panel pointer-events-auto">
    <div class="entity-browser-panel__header">
      <div>
        <div class="entity-browser-panel__kicker">Tactical View</div>
        <h3>Entities</h3>
      </div>
      <div class="entity-browser-panel__context">
        <span>{{ activeSystemId ?? "Galaxy" }}</span>
        <span>{{ povLevel }}</span>
      </div>
    </div>

    <div class="entity-browser-panel__tabs">
      <button
        type="button"
        class="entity-browser-panel__tab"
        :class="activeTab === 'visible' ? 'entity-browser-panel__tab--active' : ''"
        @click="activeTab = 'visible'"
      >
        Visible
      </button>
      <button
        type="button"
        class="entity-browser-panel__tab"
        :class="activeTab === 'owned' ? 'entity-browser-panel__tab--active' : ''"
        @click="activeTab = 'owned'"
      >
        Owned
      </button>
    </div>

    <input
      v-model="searchText"
      class="entity-browser-panel__search"
      type="text"
      placeholder="Filter entities"
    >

    <div v-if="activeTab === 'owned' && !playerFaction" class="entity-browser-panel__empty">
      No player-owned assets yet.
    </div>
    <div v-else-if="displayRows.length === 0" class="entity-browser-panel__empty">
      Nothing matches the current view.
    </div>
    <div v-else class="entity-browser-panel__sections">
      <div class="entity-browser-table-wrap">
        <table class="entity-browser-table entity-browser-table--tree">
          <colgroup>
            <col class="entity-browser-table__col entity-browser-table__col--entity">
            <col class="entity-browser-table__col entity-browser-table__col--ident">
            <col class="entity-browser-table__col entity-browser-table__col--location">
            <col class="entity-browser-table__col entity-browser-table__col--ai">
            <col class="entity-browser-table__col entity-browser-table__col--hp">
          </colgroup>
          <thead>
            <tr>
              <th scope="col">
                <button type="button" class="entity-browser-table__sort" @click="toggleSort('entity')">
                  Entity{{ sortMarker("entity") }}
                </button>
              </th>
              <th scope="col">Ident</th>
              <th scope="col">
                <button type="button" class="entity-browser-table__sort" @click="toggleSort('location')">
                  Location{{ sortMarker("location") }}
                </button>
              </th>
              <th scope="col">
                <button type="button" class="entity-browser-table__sort" @click="toggleSort('ai')">
                  AI{{ sortMarker("ai") }}
                </button>
              </th>
              <th scope="col" class="entity-browser-table__numeric">
                <button type="button" class="entity-browser-table__sort" @click="toggleSort('hp')">
                  HP{{ sortMarker("hp") }}
                </button>
              </th>
            </tr>
          </thead>
          <tbody>
            <tr
              v-for="row in displayRows"
              :key="row.key"
              class="entity-browser-table__row"
              :class="isSelected(row) ? 'entity-browser-table__row--selected' : ''"
              @click="selectItem(row)"
              @dblclick="focusItem(row)"
            >
              <td class="entity-browser-table__name">
                <div class="entity-browser-row" :style="{ paddingLeft: `${row.depth * 0.9}rem` }">
                  <button
                    v-if="row.children.length > 0"
                    type="button"
                    class="entity-browser-row__toggle"
                    @click.stop="toggleRow(row)"
                  >
                    {{ isExpanded(row) ? "-" : "+" }}
                  </button>
                  <span v-else class="entity-browser-row__toggle entity-browser-row__toggle--spacer" />
                  <span class="entity-browser-row__kind" :class="`entity-browser-row__kind--${row.kind}`">
                    {{ row.kindLabel }}
                  </span>
                  <span class="entity-browser-row__label">{{ row.name }}</span>
                </div>
              </td>
              <td class="entity-browser-table__detail entity-browser-table__cell--truncate">{{ row.ident }}</td>
              <td class="entity-browser-table__cell--truncate">{{ row.location }}</td>
              <td class="entity-browser-table__cell--ai">
                <div class="entity-browser-ai">
                  <span
                    v-for="token in row.aiStates"
                    :key="`${row.key}-${token}`"
                    class="entity-browser-ai__token"
                  >
                    {{ token }}
                  </span>
                  <span v-if="row.aiStates.length === 0" class="entity-browser-table__muted">--</span>
                </div>
              </td>
              <td class="entity-browser-table__numeric">
                {{ row.hpLabel }}
              </td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
  </section>
</template>
