<?php

namespace App\Forms;

use RuntimeException;

abstract class CommonForm
{
  private $_validation = null;
  private array $_attributes = [];
  private array $_formFields = [];
  private array $_formRules = [];
  private array $_formFilters = [];
  private array $_indexFilters = [];
  private array $_batchjobFilters = [];
  private array $_formOptions = [];
  private array $_actionButtons = ['view' => ICONS['SEARCH'], 'delete' => ICONS['DELETE']];
  private array $_batchjobButtons = ['batchjob' => '일괄처리', 'batchjob_delete' => '일괄삭제'];
  protected function __construct()
  {
    $this->_validation = service('validation');
  }
  public function action_init_process(string $action, array &$formDatas = []): void
  {
    $actionButtons = ['view' => ICONS['SEARCH'], 'delete' => ICONS['DELETE']];
    $batchjobButtons = [];
    $this->setActionButtons($actionButtons);
    $this->setBatchjobButtons($batchjobButtons);
  }
  final public function setAttributes(array $attributes): void
  {
    $this->_attributes = $attributes;
  }
  final public function getAttribute(string $key): string
  {
    if (!array_key_exists($key, $this->_attributes)) {
      throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: {$key}에 해당하는 속성이 정의되지 않았습니다.");
    }
    return $this->_attributes[$key];
  }
  final public function setFormFields(array $fields): void
  {
    foreach ($fields as $field) {
      $this->_formFields[$field] = $this->getFormFieldLabel($field);
    }
  }
  //$fields 매치된것만 반환, []->전체
  final public function getFormFields(array $fields = []): array
  {
    if (empty($fields)) {
      return $this->_formFields;
    }
    // _formFields와 키를 비교하여 교집합을 반환합니다. $fields에 지정된 필드 정의만 추출됩니다.
    return array_intersect_key($this->_formFields, array_flip($fields));
  }
  public function setFormRules(string $action, array $fields, $formRules = []): void
  {
    foreach ($fields as $field) {
      $formRules = $this->getFormRule($action, $field, $formRules);
    }
    $this->_formRules = $formRules;
  }
  final public function getFormRules(array $fields = []): array
  {
    if (empty($fields)) {
      return $this->_formRules;
    }
    return array_intersect_key($this->_formRules, array_flip($fields));
  }
  final public function setFormOptions(string $action, array $fields, array $formDatas = [], $formOptions = []): void
  {
    foreach ($fields as $field) {
      $formOptions[$field] = $formOptions[$field] ?? $this->getFormOption($action, $field, $formDatas);
    }
    $this->_formOptions = $formOptions;
  }
  //$fields 매치된것만 반환, []->전체
  final public function getFormOptions(array $fields = []): array
  {
    if (empty($fields)) {
      return $this->_formOptions;
    }
    return array_intersect_key($this->_formOptions, array_flip($fields));
  }
  final public function setFormFilters(array $fields): void
  {
    $this->_formFilters = $fields;
  }
  final public function getFormFilters(): array
  {
    return $this->_formFilters;
  }
  final public function setIndexFilters(array $fields): void
  {
    $this->_indexFilters = $fields;
    ;
  }
  final public function getIndexFilters(): array
  {
    return $this->_indexFilters;
  }
  final public function setBatchjobFilters(array $fields): void
  {
    $this->_batchjobFilters = $fields;
    ;
  }
  final public function getBatchjobFilters(): array
  {
    return $this->_batchjobFilters;
  }
  final public function setActionButtons(array $buttons): array
  {
    return $this->_actionButtons = $buttons;
  }
  final public function getActionButtons(): array
  {
    return $this->_actionButtons;
  }
  final public function setBatchjobButtons(array $buttons): array
  {
    return $this->_batchjobButtons = $buttons;
  }
  final public function getBatchjobButtons(): array
  {
    return $this->_batchjobButtons;
  }
  //Validation용
  /**
   * 데이터를 검증하고 유효하지 않을 경우 예외를 발생시킵니다.
   * 2025 CI4 표준: 규칙 배열 내에 label을 포함하여 한글 메시지 출력을 보장합니다.
   *
   * @param array $formDatas 검증할 데이터
   * @throws RuntimeException
   */
  final public function validate(array &$formDatas): void
  {
    if ($this->_validation === null) {
      throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: Validation 서비스가 초기화되지 않았습니다.");
    }

    try {
      $dynamicRules = [];

      // ✅ 0. 데이터 정제: "배열은 절대 문자열 캐스팅 금지"
      // - null만 ''로 바꿔 trim(null) 방지
      // - 배열은 그대로 유지 (role 같은 is_array 규칙 깨지지 않게)
      $sanitize = function (&$data, $path = '') use (&$sanitize) {
        foreach ($data as $key => &$value) {
          $currentField = $path ? "{$path}.{$key}" : $key;

          if (is_array($value)) {
            $sanitize($value, $currentField);
            continue;
          }

          if ($value === null) {
            $data[$key] = '';
            continue;
          }

          // 숫자/불리언은 그대로 둬도 됨. (trim은 문자열 규칙에서만 적용됨)
          // 굳이 문자열로 바꾸고 싶으면 여기서 처리 가능하지만, 배열에는 절대 적용하지 말 것.
        }
      };
      $sanitize($formDatas);

      // 1. 필드 라벨/규칙
      $formFields = $this->getFormFields();
      $formRules = $this->getFormRules();

      if (empty($formRules)) {
        throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: 지정된 Form RULE이 없습니다.");
      }

      // ✅ 1.5 배열 원소 규칙(role.*)이 존재할 경우, 부모 필드(role)가 없으면 빈 배열로 보장
      // 이렇게 해두면 엔진 내부에서 role.* 처리 중 trim(null) 류가 재발할 확률이 확 줄어듦
      foreach ($formRules as $fieldKey => $ruleDef) {
        $fieldName = is_array($ruleDef) ? $fieldKey : $fieldKey;

        // role.* 형태면 부모 role을 배열로 보장
        if (str_contains($fieldName, '.*')) {
          $parent = str_replace('.*', '', $fieldName);
          if (!array_key_exists($parent, $formDatas) || $formDatas[$parent] === '') {
            $formDatas[$parent] = [];
          } elseif (is_string($formDatas[$parent])) {
            // 혹시 문자열로 들어오면 CSV → 배열 복원 (공통 방어막)
            $formDatas[$parent] = ($formDatas[$parent] === '')
              ? []
              : explode(DEFAULTS["DELIMITER_COMMA"], $formDatas[$parent]);
          } elseif (!is_array($formDatas[$parent])) {
            $formDatas[$parent] = [];
          }
        }
      }

      foreach ($formRules as $field => $rule) {
        try {
          // 2. 필드명/규칙 추출
          list($field, $rule) = $this->getValidationRule($field, $rule);

          // 3. label 결정
          if (isset($formFields[$field])) {
            $label = $formFields[$field];
          } elseif (str_contains($field, '.*')) {
            $parentField = str_replace('.*', '', $field);
            $label = ($formFields[$parentField] ?? $field) . " 항목";
          } else {
            $label = $field;
          }

          // 4. rules 설정
          $dynamicRules[$field] = [
            'label' => $label,
            'rules' => $rule
          ];

          // ✅ 4.5 존재 보장 로직 수정
          // - 일반 필드: 없으면 '' 세팅
          // - 배열 원소 필드(role.*): 여기서 만들면 안 됨 (부모 role에서 처리해야 함)
          if (!array_key_exists($field, $formDatas) && !str_contains($field, '.*')) {
            $formDatas[$field] = '';
          }

        } catch (\Throwable $e) {
          throw new RuntimeException("유효성 검사 규칙 준비 중 오류 발생 (필드: {$field}): " . $e->getMessage());
        }
      }

      $this->_validation->setRules($dynamicRules);

      try {
        if (!$this->_validation->run($formDatas)) {
          $errors = $this->_validation->getErrors();
          throw new RuntimeException(implode("\n", $errors));
        }
      } catch (\TypeError $e) {
        // 너의 상세 디버깅 로직은 그대로 둬도 됨 (생략 가능)
        throw new RuntimeException("검증 도중 타입 오류 발생: " . $e->getMessage());
      }

    } catch (\Throwable $e) {
      if ($e instanceof RuntimeException)
        throw $e;
      throw new RuntimeException("유효성 검사 중 시스템 오류 발생: " . $e->getMessage());
    }
  }


  //필수함수
  //사용자정의 함수
  protected function getValidationRule(string $field, string $rule): array
  {
    return array($field, $rule);
  }

  public function getFormFieldLabel(string $field, ?string $label = null): string
  {
    switch ($field) {
      default:
        $label = $label ?? lang("{$this->getAttribute('class_path')}.label.{$field}");
        break;
    }
    return $label;
  }

  public function getFormRule(string $action, string $field, array $formRules): array
  {
    switch ($field) {
      case $this->getAttribute('pk_field'):
        if (!$this->getAttribute('useAutoIncrement')) {
          $formRules[$field] = sprintf("required|regex_match[/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/]%s", in_array($action, ["create"]) ? "|is_unique[{$this->getAttribute('table')}.{$field}]" : "");
        } else {
          $formRules[$field] = "required|numeric";
        }
        break;
      case $this->getAttribute('title_field'):
        $formRules[$field] = sprintf("required|trim|string%s", in_array($action, ["create", "create_form"]) ? "|is_unique[{$this->getAttribute('table')}.{$field}]" : "");
        break;
      case "code":
        // a-zA-Z → 영문 대소문자,0-9 → 숫자,가-힣 → 한글 완성형,\- → 하이픈
        $formRules[$field] = sprintf("required|regex_match[/^[a-zA-Z0-9가-힣\-\_]+$/]|min_length[4]%s", in_array($action, ["create"]) ? "|is_unique[{$this->getAttribute('table')}.{$field}]" : "");
        break;
      case "user_uid":
        $formRules[$field] = "required|numeric";
        break;
      case "clientinfo_uid":
      case "serviceinfo_uid":
      case "serverinfo_uid":
        $formRules[$field] = "permit_empty|numeric";
        break;
      case 'picture':
        $formRules[$field] = "is_image[{$field}]|mime_in[{$field},image/jpg,image/jpeg,image/gif,image/png,image/webp]|max_size[{$field},300]|max_dims[{$field},2048,768]";
        break;
      case "updated_at":
      case "created_at":
      case "deleted_at":
        $formRules[$field] = "permit_empty|trim|valid_date";
        break;
      default:
        $formRules[$field] = "permit_empty|trim|string";
        break;
    }
    return $formRules;
  }
  protected function getFormOption_process($service, string $action, string $field, array $formDatas = []): array
  {
    $entities = [];
    switch ($field) {
      default:
        if (in_array($action, ['create_form', 'modify_form', 'alternative_create_form'])) {
          if (array_key_exists($field, $formDatas)) {
            $where = sprintf("status = '%s' OR %s='%s'", STATUS['AVAILABLE'], $this->getAttribute('pk_field'), $formDatas[$field]);
          } else {
            $where = sprintf("status = '%s'", STATUS['AVAILABLE']);
          }
          $entities = $service->getEntities([$where => null]);
        } else {
          $entities = $service->getEntities();
        }
        break;
    }
    return $entities;
  }

  public function getFormOption(string $action, string $field, array $formDatas = [], array $options = ['options' => [], 'atttributes' => []]): array
  {
    $tempOptions = ['' => lang("{$this->getAttribute('class_path')}.label.{$field}") . " 선택"];
    switch ($field) {
      case 'user_uid':
        foreach ($this->getFormOption_process(service('userservice'), $action, $field, $formDatas) as $entity) {
          $tempOptions[$entity->getPK()] = $entity->getTitle();
        }
        $options['options'] = $tempOptions;
        break;
      case 'clientinfo_uid':
        foreach ($this->getFormOption_process(service('customer_clientservice'), $action, $field, $formDatas) as $entity) {
          $tempOptions[$entity->getPK()] = $entity->getCustomTitle();
        }
        $options['options'] = $tempOptions;
        break;
      default:
        $optionDatas = lang($this->getAttribute('class_path') . "." . strtoupper($field));
        if (!is_array($optionDatas)) {
          throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:{$field}가 배열값이 아닙니다.");
        }
        foreach ($optionDatas as $key => $label) {
          $tempOptions[$key] = $label;
        }
        $options['options'] = $tempOptions;
        break;
    }
    return $options;
  }
}
