import { z } from "zod";
import {
  DASHBOARD_GRID_COLUMNS,
  DASHBOARD_LAYOUT_VERSION,
  DASHBOARD_WIDGET_CATALOG,
  DASHBOARD_WIDGET_TYPES,
  type DashboardLayoutConfig,
  type DashboardWidgetCatalogEntry,
  type DashboardWidgetConfigMap,
  type DashboardWidgetInstance,
  type DashboardWidgetType,
} from "../types/dashboard.js";
import { ProjectStatus } from "../types/enums.js";

const DASHBOARD_WIDGET_BY_TYPE = Object.fromEntries(
  DASHBOARD_WIDGET_CATALOG.map((widget) => [widget.type, widget]),
) as Record<DashboardWidgetType, DashboardWidgetCatalogEntry>;

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}

function toNonEmptyString(value: unknown): string | undefined {
  if (typeof value !== "string") return undefined;
  const trimmed = value.trim();
  return trimmed.length > 0 ? trimmed : undefined;
}

function toInt(value: unknown): number | undefined {
  return typeof value === "number" && Number.isFinite(value) ? Math.trunc(value) : undefined;
}

function clamp(value: number, min: number, max: number): number {
  return Math.min(Math.max(value, min), max);
}

const dashboardWidgetTypeSchema = z.enum(DASHBOARD_WIDGET_TYPES);

const widgetChromeConfigSchema = z.object({
  showDetails: z.boolean().optional(),
});

const resourceTableWidgetConfigSchema = widgetChromeConfigSchema.extend({
  chapter: z.preprocess(toNonEmptyString, z.string().optional()),
});

const projectTableWidgetConfigSchema = widgetChromeConfigSchema.extend({
  search: z.preprocess(toNonEmptyString, z.string().optional()),
  status: z.nativeEnum(ProjectStatus).optional(),
});

const peakTimesWidgetConfigSchema = widgetChromeConfigSchema.extend({
  granularity: z.enum(["week", "month"]).optional(),
  groupBy: z.enum(["project", "chapter", "resource"]).optional(),
});

const demandWidgetConfigSchema = widgetChromeConfigSchema.extend({
  groupBy: z.enum(["project", "person", "chapter"]).optional(),
});

const topValueWidgetConfigSchema = widgetChromeConfigSchema.extend({
  limit: z.number().int().min(1).max(100).optional(),
});

const chargeabilityWidgetConfigSchema = widgetChromeConfigSchema.extend({
  topN: z.number().int().min(1).max(100).optional(),
  watchlistThreshold: z.number().int().min(0).max(100).optional(),
});

const myProjectsWidgetConfigSchema = widgetChromeConfigSchema.extend({
  showFavorites: z.boolean().optional(),
  showResponsible: z.boolean().optional(),
});

export const dashboardWidgetConfigSchemas = {
  "stat-cards": widgetChromeConfigSchema,
  "resource-table": resourceTableWidgetConfigSchema,
  "project-table": projectTableWidgetConfigSchema,
  "peak-times-chart": peakTimesWidgetConfigSchema,
  "demand-view": demandWidgetConfigSchema,
  "top-value-resources": topValueWidgetConfigSchema,
  "chargeability-overview": chargeabilityWidgetConfigSchema,
  "my-projects": myProjectsWidgetConfigSchema,
  "budget-forecast": widgetChromeConfigSchema,
  "skill-gap": widgetChromeConfigSchema,
  "project-health": widgetChromeConfigSchema,
} as const;

type DashboardWidgetConfigSchemaMap = typeof dashboardWidgetConfigSchemas;

export function getDashboardWidgetCatalogEntry(type: DashboardWidgetType): DashboardWidgetCatalogEntry {
  return DASHBOARD_WIDGET_BY_TYPE[type];
}

export function getNextDashboardWidgetY(widgets: DashboardWidgetInstance[]): number {
  return widgets.reduce((max, widget) => Math.max(max, widget.y + widget.h), 0);
}

export function normalizeDashboardWidgetConfig<T extends DashboardWidgetType>(
  type: T,
  config: unknown,
): DashboardWidgetConfigMap[T] {
  const schema = dashboardWidgetConfigSchemas[type];
  const parsed = schema.safeParse(isRecord(config) ? config : {});
  return {
    ...DASHBOARD_WIDGET_BY_TYPE[type].defaultConfig,
    ...(parsed.success ? parsed.data : {}),
  } as DashboardWidgetConfigMap[T];
}

export function createDashboardWidget<T extends DashboardWidgetType>(
  type: T,
  options: {
    id: string;
    title?: string;
    x?: number;
    y?: number;
    w?: number;
    h?: number;
    minW?: number;
    minH?: number;
    config?: unknown;
  },
): DashboardWidgetInstance<T> {
  const widgetDef = DASHBOARD_WIDGET_BY_TYPE[type];
  const minW = Math.max(1, toInt(options.minW) ?? widgetDef.minSize.w);
  const minH = Math.max(1, toInt(options.minH) ?? widgetDef.minSize.h);
  const w = clamp(Math.max(minW, toInt(options.w) ?? widgetDef.defaultSize.w), minW, DASHBOARD_GRID_COLUMNS);
  const h = Math.max(minH, toInt(options.h) ?? widgetDef.defaultSize.h);
  const title = toNonEmptyString(options.title);

  return {
    id: options.id,
    type,
    x: clamp(Math.max(0, toInt(options.x) ?? 0), 0, Math.max(0, DASHBOARD_GRID_COLUMNS - w)),
    y: Math.max(0, toInt(options.y) ?? 0),
    w,
    h,
    minW,
    minH,
    config: normalizeDashboardWidgetConfig(type, options.config),
    ...(title ? { title } : {}),
  };
}

export function createDefaultDashboardLayout(): DashboardLayoutConfig {
  return {
    version: DASHBOARD_LAYOUT_VERSION,
    gridCols: DASHBOARD_GRID_COLUMNS,
    widgets: [
      createDashboardWidget("stat-cards", {
        id: "default-stat-cards",
        x: 0,
        y: 0,
      }),
    ],
  };
}

export function normalizeDashboardLayout(input: unknown): DashboardLayoutConfig {
  if (!isRecord(input)) {
    return createDefaultDashboardLayout();
  }

  const gridCols = clamp(Math.max(1, toInt(input.gridCols) ?? DASHBOARD_GRID_COLUMNS), 1, 24);
  const rawWidgets = Array.isArray(input.widgets) ? input.widgets : [];
  const widgets: DashboardWidgetInstance[] = [];
  const seenIds = new Set<string>();
  let nextY = 0;

  rawWidgets.forEach((rawWidget, index) => {
    if (!isRecord(rawWidget)) return;

    const typeResult = dashboardWidgetTypeSchema.safeParse(rawWidget.type);
    if (!typeResult.success) return;

    const type = typeResult.data;
    const baseId = toNonEmptyString(rawWidget.id) ?? `${type}-${index + 1}`;
    let id = baseId;
    let suffix = 1;
    while (seenIds.has(id)) {
      suffix += 1;
      id = `${baseId}-${suffix}`;
    }
    seenIds.add(id);

    const widgetOptions: Parameters<typeof createDashboardWidget<typeof type>>[1] = {
      id,
      ...(typeof rawWidget.title === "string" ? { title: rawWidget.title } : {}),
      ...(typeof rawWidget.x === "number" ? { x: rawWidget.x } : {}),
      ...(typeof rawWidget.y === "number" ? { y: rawWidget.y } : {}),
      ...(typeof rawWidget.w === "number" ? { w: rawWidget.w } : {}),
      ...(typeof rawWidget.h === "number" ? { h: rawWidget.h } : {}),
      ...(typeof rawWidget.minW === "number" ? { minW: rawWidget.minW } : {}),
      ...(typeof rawWidget.minH === "number" ? { minH: rawWidget.minH } : {}),
      ...(rawWidget.config !== undefined ? { config: rawWidget.config } : {}),
    };

    const widget = createDashboardWidget(type, widgetOptions);

    const placedWidget = {
      ...widget,
      x: clamp(widget.x, 0, Math.max(0, gridCols - widget.w)),
      y: toInt(rawWidget.y) !== undefined && (toInt(rawWidget.y) ?? 0) >= 0 ? widget.y : nextY,
      w: clamp(widget.w, widget.minW, gridCols),
    };

    widgets.push(placedWidget);
    nextY = Math.max(nextY, placedWidget.y + placedWidget.h);
  });

  return {
    version: DASHBOARD_LAYOUT_VERSION,
    gridCols,
    widgets,
  };
}

export const dashboardWidgetInstanceSchema = z.object({
  id: z.string().min(1),
  type: dashboardWidgetTypeSchema,
  title: z.string().min(1).optional(),
  x: z.number().int().min(0),
  y: z.number().int().min(0),
  w: z.number().int().min(1),
  h: z.number().int().min(1),
  minW: z.number().int().min(1),
  minH: z.number().int().min(1),
  config: z.record(z.unknown()),
});

export const dashboardLayoutSchema = z
  .unknown()
  .transform((value) => normalizeDashboardLayout(value))
  .pipe(
    z.object({
      version: z.literal(DASHBOARD_LAYOUT_VERSION),
      widgets: z.array(dashboardWidgetInstanceSchema),
      gridCols: z.number().int().min(1).max(24),
    }),
  );
