import {
  SubjectType,
  VisibilityType,
  type SubjectType as SubjectTypeCode,
  type VisibilityType as VisibilityTypeCode,
} from "../../shared/api/enums";
import type { Id } from "../../shared/api/types";

export interface PermissionEntry {
  id: Id;
  subjectType: SubjectTypeCode;
  subjectId: Id;
  subjectName: string;
  canView: boolean;
  canDownload: boolean;
  canEdit: boolean;
  canManagePermission: boolean;
  canDelete: boolean;
}

export interface PermissionDetail {
  documentId: Id;
  sourceDocumentId: Id;
  inherited: boolean;
  visibilityType: VisibilityTypeCode;
  documentRowVersion: number;
  entries: PermissionEntry[];
}

export interface SavePermissionEntry {
  subjectType: SubjectTypeCode;
  subjectId: Id;
  canView: boolean;
  canDownload: boolean;
  canEdit: boolean;
  canManagePermission: boolean;
  canDelete: boolean;
}

export interface SavePermissionsRequest {
  visibilityType: VisibilityTypeCode;
  documentRowVersion: number;
  entries: SavePermissionEntry[];
}

type RecordValue = Record<string, unknown>;
const idPattern = /^[0-9]+$/;
const actionKeys = [
  "canView",
  "canDownload",
  "canEdit",
  "canManagePermission",
  "canDelete",
] as const;

function isRecord(value: unknown): value is RecordValue {
  return !!value && typeof value === "object" && !Array.isArray(value);
}

function hasExactKeys(value: RecordValue, keys: readonly string[]): boolean {
  const actual = Object.keys(value).sort();
  const expected = [...keys].sort();
  return actual.length === expected.length &&
    actual.every((key, index) => key === expected[index]);
}

function isId(value: unknown): value is Id {
  return typeof value === "string" && idPattern.test(value);
}

function isEnum(source: Record<string, string>, value: unknown): boolean {
  return typeof value === "string" && Object.values(source).includes(value);
}

function hasBooleanActions(value: RecordValue): boolean {
  return actionKeys.every((key) => typeof value[key] === "boolean");
}

export function isPermissionEntry(value: unknown): value is PermissionEntry {
  if (!isRecord(value) || !hasExactKeys(value, [
    "id",
    "subjectType",
    "subjectId",
    "subjectName",
    ...actionKeys,
  ])) {
    return false;
  }
  return (
    isId(value.id) &&
    isEnum(SubjectType, value.subjectType) &&
    isId(value.subjectId) &&
    typeof value.subjectName === "string" &&
    hasBooleanActions(value)
  );
}

export function isPermissionDetail(value: unknown): value is PermissionDetail {
  if (!isRecord(value) || !hasExactKeys(value, [
    "documentId",
    "sourceDocumentId",
    "inherited",
    "visibilityType",
    "documentRowVersion",
    "entries",
  ])) {
    return false;
  }
  return (
    isId(value.documentId) &&
    isId(value.sourceDocumentId) &&
    typeof value.inherited === "boolean" &&
    isEnum(VisibilityType, value.visibilityType) &&
    typeof value.documentRowVersion === "number" &&
    Number.isInteger(value.documentRowVersion) &&
    value.documentRowVersion >= 0 &&
    Array.isArray(value.entries) &&
    value.entries.every(isPermissionEntry)
  );
}

export function isSavePermissionEntry(
  value: unknown,
): value is SavePermissionEntry {
  if (!isRecord(value) || !hasExactKeys(value, [
    "subjectType",
    "subjectId",
    ...actionKeys,
  ])) {
    return false;
  }
  return (
    isEnum(SubjectType, value.subjectType) &&
    isId(value.subjectId) &&
    hasBooleanActions(value)
  );
}

export function isSavePermissionsRequest(
  value: unknown,
): value is SavePermissionsRequest {
  if (!isRecord(value) || !hasExactKeys(value, [
    "visibilityType",
    "documentRowVersion",
    "entries",
  ])) {
    return false;
  }
  if (
    !isEnum(VisibilityType, value.visibilityType) ||
    typeof value.documentRowVersion !== "number" ||
    !Number.isInteger(value.documentRowVersion) ||
    value.documentRowVersion < 0 ||
    !Array.isArray(value.entries) ||
    !value.entries.every(isSavePermissionEntry)
  ) {
    return false;
  }
  if (value.visibilityType === VisibilityType.ALL_AUTHENTICATED) {
    return value.entries.length === 0;
  }
  if (value.visibilityType === VisibilityType.ORGANIZATION) {
    return value.entries.length > 0 &&
      value.entries.every((entry) => entry.subjectType === SubjectType.ORG);
  }
  return true;
}

