/**
 * GET /ds-devices で使うクエリ処理。
 * 仕様の正は `d-watch-api.yaml` の /ds-devices。ソート順は D-055。
 */
import { parseMasterIdFilters } from "@/lib/api/master-id";
import { fail, type QueryParseResult } from "@/lib/api/query-result";
import { PAGINATION } from "@/lib/config";
import { includesFoldName, includesFoldPlain } from "@/lib/format/search-text";
import type { DsDevice, DsDeviceStatusFilter } from "@/lib/types";

const STATUS_FILTERS: readonly DsDeviceStatusFilter[] = ["all", "error", "ok"];

export interface DsDeviceQuery {
  q: string;
  status: DsDeviceStatusFilter;
  /**
   * ホールディングス（`groups.id`）で絞り込み。★`null` = 絞らない★（指示書AK 段階2）。
   * ★`customerId` と同時に指定されたら AND★（`d-watch-api.yaml` の `GroupId`）。
   */
  groupId: number | null;
  /** 顧客（`customers.id`）で絞り込み。★`null` = 絞らない★ */
  customerId: number | null;
  page: number;
  perPage: number;
}

function toInt(value: string | null, fallback: number): number {
  if (!value) return fallback;
  const n = Number.parseInt(value, 10);
  return Number.isNaN(n) ? fallback : n;
}

/**
 * クエリパラメータをパースする。
 * 不正な値があれば `{ ok: false }` を返す（`error` / `status` をそのまま422応答に使う。
 * どのパラメータが不正かは `error.error.details` に入る）。
 * ★group_id / customer_id を読む（指示書AK 段階2。D-053 の「受け取っても無視する」は解除）★
 *   検証は `parseMasterIdFilters()`（`lib/api/master-id.ts`）の1本に任せる。
 *   ★`Number.parseInt()` で書き直さないこと★（`1abc` が `1` として通る。D-123）。
 *
 * page / per_page は不正値でも失敗させず、既定値・上限へ丸める（/clean-logs と同じ挙動）。
 */
export function parseDsDeviceQuery(
  searchParams: URLSearchParams
): QueryParseResult<DsDeviceQuery> {
  const statusRaw = searchParams.get("status") ?? "all";
  if (!STATUS_FILTERS.includes(statusRaw as DsDeviceStatusFilter)) {
    return fail(422, "validation_failed", "statusの値が不正です", {
      status: [`${STATUS_FILTERS.join(" / ")} のいずれかを指定してください`],
    });
  }

  const masters = parseMasterIdFilters(searchParams);
  if (!masters.ok) return masters;

  const page = Math.max(1, toInt(searchParams.get("page"), 1));
  const perPage = Math.min(
    PAGINATION.maxPerPage,
    Math.max(1, toInt(searchParams.get("per_page"), PAGINATION.defaultPerPage))
  );

  return {
    ok: true,
    query: {
      q: searchParams.get("q")?.trim() ?? "",
      status: statusRaw as DsDeviceStatusFilter,
      groupId: masters.query.groupId,
      customerId: masters.query.customerId,
      page,
      perPage,
    },
  };
}

/**
 * 施設名・シリアルの部分一致（YAMLのqの定義通り）。大文字小文字を区別しない。
 *
 * ★照合は `lib/format/search-text.ts` の1本に寄せる（指示書AG D-1）★
 *   施設名は記号（中点・長音・空白・ハイフン類）も無視する = `includesFoldName`。
 *   ★シリアルは大文字小文字のみ★ = `includesFoldPlain`
 *   （D-S制御盤のシリアルは `DS-001` 形式。記号を除くと桁の境界が消える。D-2）。
 */
export function filterDsDevicesByQ(items: DsDevice[], q: string): DsDevice[] {
  if (!q) return items;
  return items.filter(
    (item) =>
      includesFoldName(item.facility_name, q) ||
      includesFoldPlain(item.serial_number, q)
  );
}

/** error = "error"（YAMLの定義通り。通信断は「異常」そのものなので別値を持たない。D-065） */
export function filterDsDevicesByStatus(
  items: DsDevice[],
  status: DsDeviceStatusFilter
): DsDevice[] {
  return items.filter((item) => {
    switch (status) {
      case "error":
        return item.connection_status === "error";
      case "ok":
        return item.connection_status === "ok";
      case "all":
        return true;
    }
  });
}

const CONNECTION_STATUS_ORDER: Record<DsDevice["connection_status"], number> = {
  error: 0,
  ok: 1,
};

/**
 * 異常→正常の順。同順位は serial_number の昇順で安定させる（D-055）。
 * ★安定化のためだけの規則で、運用上の優先度を表すものではない★
 */
export function sortDsDevices(items: DsDevice[]): DsDevice[] {
  return [...items].sort((a, b) => {
    const order =
      CONNECTION_STATUS_ORDER[a.connection_status] -
      CONNECTION_STATUS_ORDER[b.connection_status];
    if (order !== 0) return order;
    return a.serial_number < b.serial_number
      ? -1
      : a.serial_number > b.serial_number
        ? 1
        : 0;
  });
}

export interface DsDeviceSummary {
  total: number;
  ok: number;
  /** "error" の件数（YAMLの定義通り。通信断は「異常」に含まれる。D-065） */
  error: number;
}

/**
 * デバイスの現況サマリ。
 *
 * ★summary はフリートの部分集合（日付・グループ・会社）で絞る。
 *   閲覧の絞り込み（q・status・type）では絞らない。★
 *   グループ・会社マスタ導入時は、ここに絞り込みを追加する（現在は D-053 で未実装）。
 *   この区別を崩すと、F-03 で起きた「既定表示で解消済みが常に0」と同じ不具合になる（D-054）。
 */
export function summarizeDsDevices(items: DsDevice[]): DsDeviceSummary {
  const ok = items.filter((item) => item.connection_status === "ok").length;
  const error = items.filter((item) => item.connection_status === "error").length;
  const total = items.length;
  return { total, ok, error };
}
