import {
  GenericDoc,
  isGenericDocWithStoredObject,
} from "ChillDocStoreAssets/types/generic_doc";
import { StoredObject, StoredObjectStatus } from "ChillDocStoreAssets/types";
import { CreatableEntityType } from "ChillPersonAssets/types";
import { ThirdpartyCompany } from "../../../ChillThirdPartyBundle/Resources/public/types";
import { Person } from "../../../ChillPersonBundle/Resources/public/types";

export interface DateTime {
  datetime: string;
  datetime8601: string;
}

/**
 * A date representation to use when we create or update a date
 */
export interface DateTimeWrite {
  /**
   * Must be a string in format Y-m-d\TH:i:sO
   */
  datetime: string;
}

export interface Civility {
  type: "chill_main_civility";
  id: number;
  abbreviation: TranslatableString;
  active: boolean;
  name: TranslatableString;
}

/**
 * Lightweight reference to Civility, to use in POST or PUT requests.
 */
export interface SetCivility {
  type: "chill_main_civility";
  id: number;
}

/**
 * Gender translation.
 *
 * Match the GenderEnum in PHP code.
 */
export type GenderTranslation = "male" | "female" | "neutral" | "unknown";

/**
 * A gender
 *
 * See also
 */
export interface Gender {
  type: "chill_main_gender";
  id: number;
  label: TranslatableString;
  genderTranslation: GenderTranslation;
}

/**
 * Lightweight reference to a Gender, used in POST / PUT requests.
 */
export interface SetGender {
  type: "chill_main_gender";
  id: number;
}

export interface Household {
  type: "household";
  id: number;
}

export interface Job {
  id: number;
  type: "user_job";
  label: {
    fr: string; // could have other key. How to do that in ts ?
  };
}

export interface Center {
  id: number;
  type: "center";
  name: string;
  isActive: boolean;
}

/**
 * SetCenter is a lightweight reference used in POST/PUT requests to associate an existing center with a resource.
 * It links by id only and does not create or modify centers.
 * Expected shape: { type: "center", id: number }.
 * Requests will fail if the id is invalid, the center doesn't exist, or permissions are insufficient.
 */
export interface SetCenter {
  id: number;
  type: "center";
}

export interface Scope {
  id: number;
  type: "scope";
  name: {
    fr: string;
  };
}

export interface ResultItem<T> {
  result: T;
  relevance: number;
}

export interface User {
  type: "user";
  id: number;
  username: string;
  text: string;
  text_without_absence: string;
  email: string;
  user_job: Job;
  label: string;
  // todo: mainCenter; mainJob; etc..
}

export interface UserGroup {
  type: "user_group";
  id: number;
  label: TranslatableString;
  backgroundColor: string;
  foregroundColor: string;
  excludeKey: string;
  text: string;
}

export type UserGroupOrUser = User | UserGroup;

export interface UserAssociatedInterface {
  type: "user";
  id: number;
}

export type TranslatableString = Record<string, string>;

export interface Postcode {
  id: number;
  name: string;
  code: string;
  center: Point;
}

export interface Point {
  type: "Point";
  coordinates: [lat: number, lon: number];
}

export interface Country {
  id: number;
  name: TranslatableString;
  code: string;
}

export type AddressRefStatus = "match" | "to_review" | "reviewed";

export interface Address {
  type: "address";
  address_id: number;
  text: string;
  street: string;
  streetNumber: string;
  postcode: Postcode;
  country: Country;
  floor: string | null;
  corridor: string | null;
  steps: string | null;
  flat: string | null;
  buildingName: string | null;
  distribution: string | null;
  extra: string | null;
  confidential: boolean;
  lines: string[];
  addressReference: AddressReference | null;
  validFrom: DateTime;
  validTo: DateTime | null;
  point: Point | null;
  refStatus: AddressRefStatus;
  isNoAddress: boolean;
}

/**
 * Associate an existing address in write operations.
 */
export interface SetAddress {
  id: number;
}

export interface AddressWithPoint extends Address {
  point: Point;
}

export interface AddressReference {
  id: number;
  createdAt: DateTime | null;
  deletedAt: DateTime | null;
  municipalityCode: string;
  point: Point;
  postcode: Postcode;
  refId: string;
  source: string;
  street: string;
  streetNumber: string;
  updatedAt: DateTime | null;
}

export interface SimpleGeographicalUnit {
  id: number;
  layerId: number;
  unitName: string;
  unitRefId: string;
}

export interface GeographicalUnitLayer {
  id: number;
  name: TranslatableString;
  refId: string;
}

export interface Location {
  type: "location";
  id: number;
  active: boolean;
  address: Address | null;
  availableForUsers: boolean;
  createdAt: DateTime | null;
  createdBy: User | null;
  updatedAt: DateTime | null;
  updatedBy: User | null;
  email: string | null;
  name: string;
  phonenumber1: string | null;
  phonenumber2: string | null;
  locationType: LocationType;
}

export interface LocationAssociated {
  type: "location";
  id: number;
}

export interface LocationType {
  type: "location-type";
  id: number;
  active: boolean;
  addressRequired: "optional" | "required";
  availableForUsers: boolean;
  editableByUsers: boolean;
  contactData: "optional" | "required";
  title: TranslatableString;
}

export interface NewsItemType {
  id: number;
  title: string;
  content: string;
  startDate: DateTime;
  endDate: DateTime | null;
}

export interface WorkflowAvailable {
  name: string;
  text: string;
}

export interface WorkflowAttachment {
  id: number;
  relatedGenericDocKey: string;
  relatedGenericDocIdentifiers: object;
  createdAt: DateTime | null;
  createdBy: User | null;
  updatedAt: DateTime | null;
  updatedBy: User | null;
  genericDoc: null | GenericDoc;
}

export type AttachmentWithDocAndStored = WorkflowAttachment & {
  genericDoc: GenericDoc & { storedObject: StoredObject };
};

export function isAttachmentWithDocAndStored(
  a: WorkflowAttachment,
): a is AttachmentWithDocAndStored {
  return (
    isWorkflowAttachmentWithGenericDoc(a) &&
    isGenericDocWithStoredObject(a.genericDoc)
  );
}

export function isWorkflowAttachmentWithGenericDoc(
  attachment: WorkflowAttachment,
): attachment is WorkflowAttachment & { genericDoc: GenericDoc } {
  return attachment.genericDoc !== null;
}

export interface Workflow {
  name: string;
  text: string;
}

export interface EntityWorkflowStep {
  type: "entity_workflow_step";
  id: number;
  comment: string;
  currentStep: StepDefinition;
  isFinal: boolean;
  isFreezed: boolean;
  isFinalized: boolean;
  transitionPrevious: Transition | null;
  transitionAfter: Transition | null;
  previousId: number | null;
  nextId: number | null;
  transitionPreviousBy: User | null;
  transitionPreviousAt: DateTime | null;
}

export interface Transition {
  name: string;
  text: string;
  isForward: boolean;
}

export interface StepDefinition {
  name: string;
  text: string;
}

export interface EntityWorkflow {
  type: "entity_workflow";
  id: number;
  relatedEntityClass: string;
  relatedEntityId: number;
  workflow: Workflow;
  currentStep: EntityWorkflowStep;
  steps: EntityWorkflowStep[];
  datas: WorkflowData;
  title: string;
  isOnHoldAtCurrentStep: boolean;
  _permissions: {
    CHILL_MAIN_WORKFLOW_ATTACHMENT_EDIT: boolean;
  };
}

export interface WorkflowData {
  persons: Person[];
}

export interface ExportGeneration {
  id: string;
  type: "export_generation";
  exportAlias: string;
  createdBy: User | null;
  createdAt: DateTime | null;
  status: StoredObjectStatus;
  storedObject: StoredObject;
}

export interface PrivateCommentEmbeddable {
  comments: Record<number, string>;
}

// API Exception types
export interface TransportExceptionInterface {
  name: string;
}

type IndexedKey<Base extends string> = `${Base}[${number}]`;
type BaseKeys<M> = Extract<keyof M, string>;

export type DynamicKeys<M extends Record<string, Record<string, unknown>>> =
  | BaseKeys<M>
  | { [K in BaseKeys<M> as IndexedKey<K>]: K }[IndexedKey<BaseKeys<M>>];

type NormalizeKey<K extends string> = K extends `${infer B}[${number}]` ? B : K;

export type ViolationFromMap<
  M extends Record<string, Record<string, unknown>>,
> = {
  [K in DynamicKeys<M> & string]: {
    // <- note le "& string" ici
    propertyPath: K;
    title: string;
    parameters?: M[NormalizeKey<K>];
    type?: string;
  };
}[DynamicKeys<M> & string];

export type ValidationProblemFromMap<
  M extends Record<string, Record<string, string | number>>,
> = {
  type: string;
  title: string;
  detail?: string;
  violations: ViolationFromMap<M>[];
} & Record<string, unknown>;

export interface ValidationExceptionInterface<
  M extends Record<string, Record<string, string | number>> = Record<
    string,
    Record<string, string | number>
  >,
> extends Error {
  name: "ValidationException";
  /** Full server payload copy  */
  problems: ValidationProblemFromMap<M>;
  /** A list of all violations, with property key */
  violationsList: ViolationFromMap<M>[];
  /** Compact list "Title: path" */
  violations: string[];
  /** Only titles */
  titles: string[];
  /** Only property paths */
  propertyPaths: DynamicKeys<M> & string[];
  /** Indexing by property (useful for display by field) */
  byProperty: Record<Extract<keyof M, string>, string[]>;

  violationsByNormalizedProperty(
    property: Extract<keyof M, string>,
  ): ViolationFromMap<M>[];

  violationsByNormalizedPropertyAndParams<
    P extends Extract<keyof M, string>,
    K extends Extract<keyof M[P], string>,
  >(
    property: P,
    param: K,
    param_value: M[P][K],
  ): ViolationFromMap<M>[];
}

export interface AccessExceptionInterface extends TransportExceptionInterface {
  name: "AccessException";
  violations: string[];
}

export interface NotFoundExceptionInterface extends TransportExceptionInterface {
  name: "NotFoundException";
}

export interface ServerExceptionInterface extends TransportExceptionInterface {
  name: "ServerException";
  message: string;
  code: number;
  body: string;
}

export interface ConflictHttpExceptionInterface extends TransportExceptionInterface {
  name: "ConflictHttpException";
  violations: string[];
}

export type ApiException =
  | ValidationExceptionInterface
  | AccessExceptionInterface
  | NotFoundExceptionInterface
  | ServerExceptionInterface
  | ConflictHttpExceptionInterface;

export interface Modal {
  showModal: boolean;
  modalDialogClass: string;
}

export interface Selected {
  result: UserGroupOrUser;
}

export interface addNewEntities {
  selected: Selected[];
  modal: Modal;
}

export enum HomepageTabs {
  MyCustoms,
  MyTickets,
  MyNotifications,
  MyAccompanyingCourses,
  MyEvaluations,
  MyTasks,
  MyWorkflows,
}

export interface HomepageConfig {
  defaultTab: HomepageTabs;
  displayTabs: HomepageTabs[];
}

export interface TabDefinition {
  key: HomepageTabs;
  label: string;
  icon: string | null;
  counter: () => number;
}

export interface CreateComponentConfigGeneral {
  action: "create";
  allowedTypes: CreatableEntityType[];
  query: string;
  parent: null;
}

export interface CreateComponentThirdPartyAddContact {
  action: "addContact";
  allowedTypes: readonly ["thirdparty"];
  query: string;
  parent: ThirdpartyCompany;
}

/**
 * Configuration for the CreateModal and Create component
 */
export type CreateComponentConfig =
  | CreateComponentConfigGeneral
  | CreateComponentThirdPartyAddContact;

/**
 * Possible states for the WaitingScreen Component.
 */
export type WaitingScreenState = "pending" | "failure" | "stopped" | "ready";
