//* 전역 ClassValidatorPipe 가 whitelist + forbidNonWhitelisted 라 모든 칸에 데코레이터가 있어야 한다.
import { Type } from 'class-transformer';
import {
  Allow,
  ArrayMaxSize,
  IsArray,
  IsBoolean,
  IsIn,
  IsInt,
  IsNotEmpty,
  IsNumber,
  IsObject,
  IsOptional,
  IsString,
  Matches,
  Max,
  MaxLength,
  Min,
  ValidateNested,
} from 'class-validator';
import { DOC_KINDS } from './case-report.types';
import { MAX_EDITS } from './case-report.edits';

const DATE = /^\d{4}-\d{2}-\d{2}$/;
export const READ_MODES = ['auto', 'text', 'ocr'];

export class BenchmarksDto {
  @IsOptional() @IsInt() @Min(0) cancer_diagnosis?: number;
  @IsOptional() @IsInt() @Min(0) ischemic_heart_diagnosis?: number;
  @IsOptional() @IsInt() @Min(0) disease_surgery?: number;
  @IsOptional() @IsNumber() @Min(0) death_injury_ratio?: number;
  @IsOptional() @IsInt() @Min(0) dementia_age?: number;
  @IsOptional() @IsInt() @Min(0) daily_benefit_excess?: number;
}

//* 키는 워커 입력(명세 4.5 절) 그대로 snake_case — 서버는 바꾸지 않고 넘긴다
export class AnalysisOptionsDto {
  @IsOptional() @IsIn(['AUTO', 'DB', 'PDF', 'NONE']) hira_source?: string;
  @IsOptional() @IsBoolean() show_claim_deadline?: boolean;
  @IsOptional() @IsInt() @Min(1) @Max(10) deadline_years?: number;
  @IsOptional() @IsIn(['AUTO', 'DB', 'PDF', 'OFF']) use_nts?: string;
  @IsOptional() @IsInt() @Min(0) @Max(7) link_window_days?: number;
  @IsOptional() @IsInt() @Min(0) @Max(90) unlinked_block_days?: number;
  @IsOptional() @IsBoolean() pharmacy_follows_prescriber?: boolean;
  @IsOptional() @IsIn(['REVIEW', 'USE']) generic_unconfirmed_policy?: string;
  @IsOptional() @IsIn(['BLOCK', 'WARN']) identity_policy?: string;
  @IsOptional() @IsBoolean() require_complete_pages?: boolean;
  @IsOptional() @IsBoolean() require_contract_scope_all?: boolean;
  @IsOptional() @IsBoolean() include_out_of_coverage?: boolean;
  @IsOptional() @IsBoolean() estimate_payout?: boolean;
  @IsOptional() @IsBoolean() estimate_silson_coverage?: boolean;
  @IsOptional()
  @ValidateNested()
  @Type(() => BenchmarksDto)
  benchmarks?: BenchmarksDto;
}

export class CreateJobDto {
  @IsOptional() @IsString() @MaxLength(60) title?: string;
  @IsOptional() @IsString() @IsNotEmpty() @MaxLength(30) customerName?: string;
  @IsOptional() @Matches(DATE) customerBirth?: string;
  @IsOptional() @IsInt() @Min(1) hiraOddId?: number;
  @IsOptional() @IsInt() @Min(1) ntsLsaId?: number;
  @IsOptional()
  @ValidateNested()
  @Type(() => AnalysisOptionsDto)
  analysisOptions?: AnalysisOptionsDto;
}

export class PatchJobDto {
  @IsOptional() @IsString() @MaxLength(60) title?: string;
  @IsOptional() @IsString() @IsNotEmpty() @MaxLength(30) customerName?: string;
  @IsOptional() @Matches(DATE) customerBirth?: string | null;
  @IsOptional()
  @ValidateNested()
  @Type(() => AnalysisOptionsDto)
  analysisOptions?: AnalysisOptionsDto;
  @IsOptional() @Matches(DATE) refDate?: string | null;
  @IsOptional() @IsBoolean() deleteOriginalAfterExtract?: boolean;
}

export class HiraSourceDto {
  @IsInt() @Min(1) oddId!: number;
}

export class NtsSourceDto {
  @IsInt() @Min(1) lsaId!: number;
}

export class HiraListQueryDto {
  @IsOptional() @IsString() @MaxLength(30) search?: string;
}

export class NtsListQueryDto {
  @IsOptional() @Type(() => Number) @IsInt() usrId?: number;
}

//* multipart 글자 칸은 문자열로 온다
export class UploadDto {
  @IsOptional() @IsString() @MaxLength(64) password?: string;
  @IsOptional() @IsIn(READ_MODES) readMode?: string;
  @IsOptional() @Type(() => Number) @IsIn([200, 300]) ocrDpi?: number;
  @IsOptional() @IsIn([...DOC_KINDS]) kindHint?: string;
  @IsOptional() @IsIn(['GENERIC']) readerHint?: string;
}

export class PublicUploadDto {
  @IsOptional() @IsString() @MaxLength(64) password?: string;
}

export class ExtractDto {
  @IsOptional() @IsIn(READ_MODES) readMode?: string;
  @IsOptional() @Type(() => Number) @IsIn([200, 300]) ocrDpi?: number;
  @IsOptional() @IsIn([...DOC_KINDS]) kindHint?: string | null;
  @IsOptional() @IsIn(['GENERIC']) readerHint?: string | null;
  @IsOptional() @IsString() @MaxLength(64) password?: string;
}

export class PasswordDto {
  @IsString() @IsNotEmpty() @MaxLength(64) password!: string;
}

//* op·path 는 서비스가 editIndex 와 함께 거른다(400 CR_EDIT_INVALID) — 여기서는 글자인지만 본다
export class EditDto {
  @IsString() op!: string;
  @IsString() @MaxLength(300) path!: string;
  @Allow() value!: unknown;
}

export class EditsDto {
  @IsArray()
  @ArrayMaxSize(MAX_EDITS)
  @ValidateNested({ each: true })
  @Type(() => EditDto)
  edits!: EditDto[];
}

export class ManualPaymentDto {
  @IsOptional() @IsString() @MaxLength(40) id?: string;
  @IsString() @IsNotEmpty() @MaxLength(30) insurer!: string;
  @IsOptional() @IsIn(['INPATIENT', 'OUTPATIENT', 'PHARMACY']) care?: string;
  @IsOptional() @IsIn(['SILSON', 'FIXED']) kind?: string;
  @IsOptional() @IsString() @MaxLength(100) coverage?: string | null;
  @IsOptional() @IsString() @MaxLength(100) hospital?: string | null;
  @Matches(DATE) start!: string;
  @IsOptional() @Matches(DATE) end?: string | null;
  @IsOptional() @IsInt() @Min(0) amount?: number | null;
  @IsOptional() @Matches(DATE) pay_date?: string | null;
  @IsOptional() @IsString() @MaxLength(200) note?: string | null;
}

export class ManualPaymentsDto {
  @IsArray()
  @ArrayMaxSize(500)
  @ValidateNested({ each: true })
  @Type(() => ManualPaymentDto)
  items!: ManualPaymentDto[];
}

export class OverrideDto {
  @IsString() @IsNotEmpty() @MaxLength(64) itemKey!: string;
  @IsString() @IsNotEmpty() @MaxLength(30) insurer!: string;
  @IsIn(['PAID', 'NOT_PAID']) status!: string;
  @IsOptional() @IsString() @MaxLength(200) note?: string | null;
}

export class OverridesDto {
  @IsArray()
  @ArrayMaxSize(1000)
  @ValidateNested({ each: true })
  @Type(() => OverrideDto)
  items!: OverrideDto[];
}

export class HospitalAliasDto {
  @IsString() @IsNotEmpty() @MaxLength(100) hiraName!: string;
  @IsString() @IsNotEmpty() @MaxLength(100) statementName!: string;
}

export class HospitalAliasesDto {
  @IsArray()
  @ArrayMaxSize(500)
  @ValidateNested({ each: true })
  @Type(() => HospitalAliasDto)
  items!: HospitalAliasDto[];
}

//* 끝 날 비움(null)은 유지 중 — 칸 검사(형식·끝 ≥ 가입일)는 서비스가 한다
export class ManualContractDto {
  @IsString() @IsNotEmpty() @MaxLength(30) insurer!: string;
  @Matches(DATE) start!: string;
  @IsOptional() @Matches(DATE) end?: string | null;
}

export class ManualContractsDto {
  @IsArray()
  @ArrayMaxSize(100)
  // ValidateNested 는 배열 원소를 또 배열로 펼쳐 [[]] 를 통과시킨다 — 원소가 객체인지 먼저 본다(Codex r11 5)
  @IsObject({ each: true })
  @ValidateNested({ each: true })
  @Type(() => ManualContractDto)
  contracts!: ManualContractDto[];
}

export const OUTCOMES = [
  'ALREADY_CLAIMED',
  'CLAIMED',
  'PAID',
  'DENIED',
  'NOT_CLAIMING',
];

export class OutcomeDto {
  @IsString() @IsNotEmpty() @MaxLength(64) itemKey!: string;
  @IsOptional() @IsString() @MaxLength(30) insurer?: string | null;
  @IsIn(OUTCOMES) outcome!: string;
  @IsOptional() @IsString() @MaxLength(200) note?: string | null;
  @IsInt() @Min(1) resultVersion!: number;
}

export class OutcomesDto {
  @IsArray()
  @ArrayMaxSize(1000)
  @ValidateNested({ each: true })
  @Type(() => OutcomeDto)
  items!: OutcomeDto[];
}

export class CreateLinkDto {
  @IsOptional() @IsInt() @Min(1) @Max(168) expiresInHours?: number;
  @IsOptional() @IsString() @MaxLength(200) note?: string;
}
