import React from 'react';

type DecisionOption = {
  code: string;
  label: string;
  tone: string;
  helper: string;
  requireComment: boolean;
};

type WriteTargetInfo = {
  paramName: string;
  raw: any;
  candidates: string[];
  displayValue: string;
};

export class CodeInApplication implements CodeInComp {
  description() {
    return 'Approval Decision Panel - reusable Yeeflow approval-form decision block with comments and validation.';
  }

  requiredFields(params?: CodeInParams) {
    const required: string[] = [];
    const safeParams: any = params || {};
    const targetMap: any = {};
    ['decisionField', 'commentField'].forEach((key) => {
      const candidates = this.collectRequiredFieldCandidates(safeParams[key]).filter((value) => this.isLikelyWritableTargetName(value));
      if (candidates.length > 0) {
        targetMap[key] = candidates[0];
      }
      candidates.forEach((value) => {
        if (value && required.indexOf(value) === -1) {
          required.push(value);
        }
      });
    });
    /*
      Expression-editor writable targets may evaluate to the target's current value
      before render(). requiredFields(params) receives raw configuration early enough
      to capture the chosen approval form field or variable key.
    */
    (this as any).approvalDecisionTargetMap = targetMap;
    return required;
  }

  inputParameters(): InputParameter[] {
    return [
      {
        id: 'decisionField',
        name: 'Decision Field',
        type: 'variable',
        desc: 'Writable approval form field or variable used to save the selected decision value.',
      },
      {
        id: 'commentField',
        name: 'Comment Field',
        type: 'variable',
        desc: 'Writable approval form field or variable used to save approver comments or reason text.',
      },
      {
        id: 'titleText',
        name: 'Title Text',
        type: 'string',
        desc: 'Panel title shown above the decision controls.',
      },
      {
        id: 'subtitleText',
        name: 'Subtitle Text',
        type: 'string',
        desc: 'Optional helper text shown below the title.',
      },
      {
        id: 'approveLabel',
        name: 'Approve Label',
        type: 'string',
        desc: 'Button label for the approve decision. Default is Approve.',
      },
      {
        id: 'rejectLabel',
        name: 'Reject Label',
        type: 'string',
        desc: 'Button label for the reject decision. Default is Reject.',
      },
      {
        id: 'reviseLabel',
        name: 'Request Changes Label',
        type: 'string',
        desc: 'Button label for the request-changes decision. Default is Request Changes.',
      },
      {
        id: 'defaultDecision',
        name: 'Default Decision',
        type: 'variable',
        desc: 'Optional default decision code, such as approve, reject, or revise. Used only when no saved value exists.',
      },
      {
        id: 'requireCommentOnReject',
        name: 'Require Comment On Reject',
        type: 'variable',
        desc: 'Whether Reject requires a comment before the decision value is saved. Supports true/false or dynamic expression.',
      },
      {
        id: 'requireCommentOnRevise',
        name: 'Require Comment On Request Changes',
        type: 'variable',
        desc: 'Whether Request Changes requires a comment before the decision value is saved. Supports true/false or dynamic expression.',
      },
      {
        id: 'showCommentBox',
        name: 'Show Comment Box',
        type: 'variable',
        desc: 'Whether the comment/reason text box is shown. Supports true/false or dynamic expression.',
      },
      {
        id: 'commentPlaceholder',
        name: 'Comment Placeholder',
        type: 'string',
        desc: 'Placeholder text shown inside the comment box.',
      },
      {
        id: 'panelStyle',
        name: 'Panel Style',
        type: 'string',
        desc: 'Visual style: standard, compact, or bordered. Default is standard.',
      },
      {
        id: 'showStatusSummary',
        name: 'Show Status Summary',
        type: 'variable',
        desc: 'Whether selected decision summary text is shown. Supports true/false or dynamic expression.',
      },
      {
        id: 'readonlyText',
        name: 'Readonly Text',
        type: 'string',
        desc: 'Text shown when the control is readonly and no decision has been saved.',
      },
      {
        id: 'decisionOptionsJson',
        name: 'Decision Options JSON',
        type: 'variable',
        desc: 'Optional JSON array for custom decisions. Each item can include code, label, tone, helper, and requireComment.',
      },
      {
        id: 'validationMessageReject',
        name: 'Reject Validation Message',
        type: 'string',
        desc: 'Optional message shown when Reject requires a comment.',
      },
      {
        id: 'validationMessageRevise',
        name: 'Request Changes Validation Message',
        type: 'string',
        desc: 'Optional message shown when Request Changes requires a comment.',
      },
    ];
  }

  render(context: CodeInContext, fieldsValues: any, readonly: boolean) {
    return (
      <ApprovalDecisionPanel
        context={context}
        fieldsValues={fieldsValues}
        readonly={readonly}
        params={(context && context.params) || {}}
        configuredOutputTargets={(this as any).approvalDecisionTargetMap || {}}
      />
    );
  }

  normalizeExpressionText(value: any) {
    if (value === undefined || value === null) {
      return '';
    }
    if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
      return String(value).trim();
    }
    if (Array.isArray(value)) {
      return value.map((item) => this.normalizeExpressionText(item)).filter(Boolean).join(',');
    }
    if (typeof value === 'object') {
      return this.normalizeExpressionText(
        value.value !== undefined ? value.value :
        value.Value !== undefined ? value.Value :
        value.key !== undefined ? value.key :
        value.Key !== undefined ? value.Key :
        value.id !== undefined ? value.id :
        value.Id !== undefined ? value.Id :
        value.fieldId !== undefined ? value.fieldId :
        value.FieldId !== undefined ? value.FieldId :
        value.name !== undefined ? value.name :
        value.Name !== undefined ? value.Name :
        value.label !== undefined ? value.label :
        value.Label !== undefined ? value.Label :
        value.title !== undefined ? value.title :
        value.Title !== undefined ? value.Title : ''
      );
    }
    return String(value).trim();
  }

  collectRequiredFieldCandidates(value: any) {
    const candidates: string[] = [];
    const seen: any = {};
    const add = (candidate: any) => {
      const cleaned = this.normalizeExpressionText(candidate);
      if (cleaned && !seen[cleaned]) {
        seen[cleaned] = true;
        candidates.push(cleaned);
      }
    };
    const visit = (current: any, depth: number) => {
      if (current === undefined || current === null || depth > 3) {
        return;
      }
      if (typeof current === 'string' || typeof current === 'number' || typeof current === 'boolean') {
        add(current);
        return;
      }
      if (Array.isArray(current)) {
        current.forEach((item) => visit(item, depth + 1));
        return;
      }
      if (typeof current !== 'object') {
        return;
      }
      [
        'fieldId', 'FieldId', 'fieldName', 'FieldName',
        'variableId', 'VariableId', 'variableName', 'VariableName',
        'tempVariableId', 'TempVariableId', 'tempVariableName', 'TempVariableName',
        'tempVarId', 'TempVarId', 'id', 'Id', 'key', 'Key',
        'name', 'Name', 'code', 'Code', 'label', 'Label', 'title', 'Title',
        'path', 'Path', 'value', 'Value',
      ].forEach((key) => {
        if (current[key] !== undefined && current[key] !== null) {
          add(current[key]);
        }
      });
      ['target', 'Target', 'binding', 'Binding', 'field', 'Field', 'variable', 'Variable', 'tempVariable', 'TempVariable', 'data', 'Data', 'meta', 'Meta', 'metadata', 'Metadata'].forEach((key) => {
        if (current[key] !== undefined && current[key] !== null) {
          visit(current[key], depth + 1);
        }
      });
    };
    visit(value, 0);
    return candidates;
  }

  isLikelyWritableTargetName(candidate: any) {
    const value = this.normalizeExpressionText(candidate);
    if (!value || value === '[]' || value === '{}') {
      return false;
    }
    if (value.charAt(0) === '[' || value.charAt(0) === '{') {
      return false;
    }
    if (value.indexOf('":') !== -1 || value.indexOf('","') !== -1) {
      return false;
    }
    return value.length <= 160;
  }
}

class ApprovalDecisionPanel extends React.Component<any, any> {
  constructor(props: any) {
    super(props);
    const config = this.getConfigFromProps(props);
    const savedDecision = this.readCurrentTargetValue(config.decisionTarget);
    const savedComment = this.readCurrentTargetValue(config.commentTarget);
    const initialDecision = this.normalizeDecisionCode(savedDecision) || this.normalizeDecisionCode(config.defaultDecision);
    const initialComment = this.toCleanString(savedComment);
    this.state = {
      decision: initialDecision,
      comment: initialComment,
      validationMessage: this.getValidationMessage(initialDecision, initialComment, config),
    };
  }

  componentDidUpdate(previousProps: any) {
    if (this.getConfigKey(previousProps) !== this.getConfigKey(this.props)) {
      const config = this.getConfig();
      const savedDecision = this.readCurrentTargetValue(config.decisionTarget);
      const savedComment = this.readCurrentTargetValue(config.commentTarget);
      const nextDecision = this.normalizeDecisionCode(savedDecision) || this.normalizeDecisionCode(config.defaultDecision);
      const nextComment = this.toCleanString(savedComment);
      this.setState({
        decision: nextDecision,
        comment: nextComment,
        validationMessage: this.getValidationMessage(nextDecision, nextComment, config),
      });
    }
  }

  getConfig() {
    return this.getConfigFromProps(this.props);
  }

  getConfigFromProps(props: any) {
    const params = (props && props.params) || {};
    const decisionTarget = this.resolveWriteTargetFromProps(props, 'decisionField', params.decisionField);
    const commentTarget = this.resolveWriteTargetFromProps(props, 'commentField', params.commentField);
    const requireReject = this.toBoolean(params.requireCommentOnReject, true);
    const requireRevise = this.toBoolean(params.requireCommentOnRevise, true);
    return {
      decisionTarget: decisionTarget,
      commentTarget: commentTarget,
      titleText: this.toCleanString(params.titleText) || 'Approval Decision',
      subtitleText: this.toCleanString(params.subtitleText) || 'Select your decision and add a comment when needed.',
      approveLabel: this.toCleanString(params.approveLabel) || 'Approve',
      rejectLabel: this.toCleanString(params.rejectLabel) || 'Reject',
      reviseLabel: this.toCleanString(params.reviseLabel) || 'Request Changes',
      defaultDecision: this.toCleanString(params.defaultDecision),
      requireCommentOnReject: requireReject,
      requireCommentOnRevise: requireRevise,
      showCommentBox: this.toBoolean(params.showCommentBox, true),
      commentPlaceholder: this.toCleanString(params.commentPlaceholder) || 'Add a comment or reason...',
      panelStyle: this.normalizePanelStyle(params.panelStyle),
      showStatusSummary: this.toBoolean(params.showStatusSummary, true),
      readonlyText: this.toCleanString(params.readonlyText) || 'No approval decision has been captured yet.',
      validationMessageReject: this.toCleanString(params.validationMessageReject) || 'Please add a comment before rejecting.',
      validationMessageRevise: this.toCleanString(params.validationMessageRevise) || 'Please add a comment before requesting changes.',
      options: this.buildDecisionOptions(params, requireReject, requireRevise),
    };
  }

  getConfigKey(props: any) {
    const params = (props && props.params) || {};
    const configuredTargets = (props && props.configuredOutputTargets) || {};
    try {
      return JSON.stringify({
        decisionField: this.toCleanString(configuredTargets.decisionField || params.decisionField),
        commentField: this.toCleanString(configuredTargets.commentField || params.commentField),
        defaultDecision: this.toCleanString(params.defaultDecision),
        decisionOptionsJson: this.toCleanString(this.resolveParameterValue(params.decisionOptionsJson)),
      });
    } catch (error) {
      return String(new Date().getTime());
    }
  }

  buildDecisionOptions(params: any, requireReject: boolean, requireRevise: boolean): DecisionOption[] {
    const configured = this.parseDecisionOptions(params.decisionOptionsJson);
    if (configured.length > 0) {
      return configured;
    }
    return [
      {
        code: 'approve',
        label: this.toCleanString(params.approveLabel) || 'Approve',
        tone: 'approve',
        helper: 'Confirm this request can continue.',
        requireComment: false,
      },
      {
        code: 'reject',
        label: this.toCleanString(params.rejectLabel) || 'Reject',
        tone: 'reject',
        helper: 'Decline this request with a reason.',
        requireComment: requireReject,
      },
      {
        code: 'revise',
        label: this.toCleanString(params.reviseLabel) || 'Request Changes',
        tone: 'revise',
        helper: 'Send back for correction or more information.',
        requireComment: requireRevise,
      },
    ];
  }

  parseDecisionOptions(rawValue: any): DecisionOption[] {
    const resolved = this.resolveParameterValue(rawValue);
    const parsed = this.safeParseJson(resolved);
    const source = Array.isArray(parsed) ? parsed : Array.isArray(resolved) ? resolved : [];
    const result: DecisionOption[] = [];
    source.forEach((item: any, index: number) => {
      if (!item) {
        return;
      }
      if (typeof item === 'string') {
        const code = this.normalizeDecisionCode(item);
        if (code) {
          result.push({
            code: code,
            label: this.toTitleLabel(code),
            tone: this.normalizeTone(code),
            helper: '',
            requireComment: false,
          });
        }
        return;
      }
      if (typeof item === 'object') {
        const code = this.normalizeDecisionCode(this.firstDefined([item.code, item.value, item.key, item.id, item.name]));
        const label = this.toCleanString(this.firstDefined([item.label, item.title, item.text, item.name])) || this.toTitleLabel(code);
        if (!code || !label) {
          return;
        }
        result.push({
          code: code,
          label: label,
          tone: this.normalizeTone(this.toCleanString(this.firstDefined([item.tone, item.color, item.type])) || code),
          helper: this.toCleanString(this.firstDefined([item.helper, item.description, item.subtitle])),
          requireComment: this.toBoolean(this.firstDefined([item.requireComment, item.requiresComment, item.commentRequired]), false),
        });
      }
    });
    return this.dedupeOptions(result);
  }

  dedupeOptions(options: DecisionOption[]) {
    const seen: any = {};
    const result: DecisionOption[] = [];
    options.forEach((option) => {
      const code = this.normalizeDecisionCode(option && option.code);
      if (!code || seen[code]) {
        return;
      }
      seen[code] = true;
      result.push({
        code: code,
        label: this.toCleanString(option.label) || this.toTitleLabel(code),
        tone: this.normalizeTone(option.tone || code),
        helper: this.toCleanString(option.helper),
        requireComment: option.requireComment === true,
      });
    });
    return result;
  }

  handleDecisionClick(code: string) {
    if (this.props.readonly) {
      return;
    }
    const config = this.getConfig();
    const nextDecision = this.normalizeDecisionCode(code);
    const nextComment = this.state.comment || '';
    const validationMessage = this.getValidationMessage(nextDecision, nextComment, config);
    this.setState({
      decision: nextDecision,
      validationMessage: validationMessage,
    });
    this.persistOutputs(nextDecision, nextComment, config, validationMessage);
  }

  handleCommentChange(event: any) {
    if (this.props.readonly) {
      return;
    }
    const config = this.getConfig();
    const nextComment = event && event.target ? event.target.value : '';
    const validationMessage = this.getValidationMessage(this.state.decision, nextComment, config);
    this.setState({
      comment: nextComment,
      validationMessage: validationMessage,
    });
    this.persistOutputs(this.state.decision, nextComment, config, validationMessage);
  }

  persistOutputs(decision: string, comment: string, config: any, validationMessage: string) {
    if (config.commentTarget.candidates.length > 0) {
      this.writeTargetValue(config.commentTarget, comment || '');
    }
    if (decision && !validationMessage && config.decisionTarget.candidates.length > 0) {
      this.writeTargetValue(config.decisionTarget, decision);
    }
    if (decision && validationMessage && config.decisionTarget.candidates.length > 0) {
      this.writeTargetValue(config.decisionTarget, '');
    }
    if (!decision && config.decisionTarget.candidates.length > 0) {
      this.writeTargetValue(config.decisionTarget, '');
    }
  }

  getValidationMessage(decision: string, comment: string, config: any) {
    const option = this.findOption(decision, config.options);
    if (!option) {
      return '';
    }
    const needsComment = option.requireComment === true ||
      (option.code === 'reject' && config.requireCommentOnReject) ||
      (option.code === 'revise' && config.requireCommentOnRevise);
    if (needsComment && !this.toCleanString(comment)) {
      if (option.code === 'reject') {
        return config.validationMessageReject;
      }
      if (option.code === 'revise') {
        return config.validationMessageRevise;
      }
      return 'Please add a comment before using this decision.';
    }
    return '';
  }

  findOption(code: string, options: DecisionOption[]) {
    const normalized = this.normalizeDecisionCode(code);
    for (let index = 0; index < (options || []).length; index += 1) {
      if (options[index].code === normalized) {
        return options[index];
      }
    }
    return null;
  }

  readCurrentTargetValue(target: WriteTargetInfo) {
    const candidates = target && target.candidates ? target.candidates : [];
    for (let index = 0; index < candidates.length; index += 1) {
      const value = this.readCurrentValue(candidates[index]);
      if (value !== undefined && value !== null && this.toCleanString(value) !== '') {
        return value;
      }
    }
    return '';
  }

  readCurrentValue(fieldName: string): any {
    const context = this.props.context || {};
    const fieldsValues = this.props.fieldsValues || {};
    if (!fieldName) {
      return undefined;
    }
    if (fieldsValues && fieldsValues[fieldName] !== undefined) {
      return fieldsValues[fieldName];
    }
    if (context && typeof context.getFieldValue === 'function') {
      try {
        return context.getFieldValue(fieldName);
      } catch (error) {
        return undefined;
      }
    }
    return undefined;
  }

  writeTargetValue(target: WriteTargetInfo, value: any) {
    const candidates = target && target.candidates ? target.candidates : [];
    for (let index = 0; index < candidates.length; index += 1) {
      if (this.writeFieldValue(candidates[index], value)) {
        return true;
      }
    }
    return false;
  }

  writeFieldValue(fieldName: string, value: any) {
    const context = this.props.context || {};
    const fieldsValues = this.props.fieldsValues || {};
    if (!fieldName) {
      return false;
    }

    const writerNames = [
      'setFieldValue',
      'setFormFieldValue',
      'setFieldData',
      'setValue',
      'setVariableValue',
      'setVariable',
      'setTempVariableValue',
      'setTempVariable',
      'setTemporaryVariableValue',
      'setTemporaryVariable',
      'updateFieldValue',
      'updateVariableValue',
    ];
    const setterHosts = this.getSetterHosts(context);

    for (let hostIndex = 0; hostIndex < setterHosts.length; hostIndex += 1) {
      const host = setterHosts[hostIndex].host;
      for (let writerIndex = 0; writerIndex < writerNames.length; writerIndex += 1) {
        const writer = host && host[writerNames[writerIndex]];
        if (typeof writer === 'function') {
          try {
            const result = writer.call(host, fieldName, value);
            if (result !== false) {
              return true;
            }
          } catch (error) {
            /* Continue trying other setter paths. */
          }
        }
      }
    }

    const objectSetterNames = ['setFieldsValue', 'setFieldValues', 'setValues', 'setVariables', 'setTempVariables'];
    for (let hostIndex = 0; hostIndex < setterHosts.length; hostIndex += 1) {
      const host = setterHosts[hostIndex].host;
      for (let writerIndex = 0; writerIndex < objectSetterNames.length; writerIndex += 1) {
        const writer = host && host[objectSetterNames[writerIndex]];
        if (typeof writer === 'function') {
          try {
            const payload: any = {};
            payload[fieldName] = value;
            const result = writer.call(host, payload);
            if (result !== false) {
              return true;
            }
          } catch (error) {
            /* Continue trying other setter paths. */
          }
        }
      }
    }

    fieldsValues[fieldName] = value;
    return true;
  }

  getSetterHosts(context: any) {
    const hosts: any[] = [];
    const addHost = (name: string, host: any) => {
      if (host && hosts.filter((item) => item.host === host).length === 0) {
        hosts.push({ name: name, host: host });
      }
    };
    addHost('context', context);
    addHost('context.formContext', context && context.formContext);
    addHost('context.pageContext', context && context.pageContext);
    addHost('context.variableContext', context && context.variableContext);
    addHost('context.variables', context && context.variables);
    addHost('context.tempVariables', context && context.tempVariables);
    addHost('context.runtimeContext', context && context.runtimeContext);
    addHost('context.dataContext', context && context.dataContext);
    return hosts;
  }

  resolveWriteTargetFromProps(props: any, paramName: string, rawValue: any): WriteTargetInfo {
    const configuredTargets = (props && props.configuredOutputTargets) || {};
    const requiredFieldFallbackTarget = this.getRequiredFieldFallbackTarget(props, paramName);
    const candidates = this.mergeTargetCandidates(
      this.collectWritableTargetCandidates(configuredTargets[paramName]),
      this.mergeTargetCandidates(
        this.collectWritableTargetCandidates(rawValue),
        this.collectWritableTargetCandidates(requiredFieldFallbackTarget)
      )
    ).filter((candidate) => this.isLikelyWritableTargetName(candidate));
    return {
      paramName: paramName,
      raw: rawValue,
      candidates: candidates,
      displayValue: candidates.length > 0 ? candidates[0] : this.toCleanString(rawValue),
    };
  }

  getRequiredFieldFallbackTarget(props: any, paramName: string) {
    const fieldsValues = (props && props.fieldsValues) || {};
    const keys = Object.keys(fieldsValues).filter((key) => this.isLikelyWritableTargetName(key));
    const indexMap: any = {
      decisionField: 0,
      commentField: 1,
    };
    const index = indexMap[paramName];
    return index !== undefined && keys[index] ? keys[index] : '';
  }

  collectWritableTargetCandidates(rawValue: any) {
    const candidates: string[] = [];
    const seen: any = {};
    const addCandidate = (value: any) => {
      const cleaned = this.toCleanString(value);
      if (!cleaned || seen[cleaned]) {
        return;
      }
      seen[cleaned] = true;
      candidates.push(cleaned);
    };

    const visit = (value: any, depth: number) => {
      if (value === undefined || value === null || depth > 4) {
        return;
      }
      if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
        addCandidate(value);
        this.extractExpressionTokens(value).forEach(addCandidate);
        return;
      }
      if (Array.isArray(value)) {
        value.forEach((item) => visit(item, depth + 1));
        return;
      }
      if (typeof value !== 'object') {
        return;
      }
      [
        'fieldId', 'FieldId', 'fieldName', 'FieldName',
        'variableId', 'VariableId', 'variableName', 'VariableName',
        'tempVariableId', 'TempVariableId', 'tempVariableName', 'TempVariableName',
        'tempVarId', 'TempVarId', 'id', 'Id', 'key', 'Key',
        'name', 'Name', 'code', 'Code', 'label', 'Label', 'title', 'Title',
        'path', 'Path', 'value', 'Value',
      ].forEach((key) => {
        if (value[key] !== undefined && value[key] !== null) {
          addCandidate(value[key]);
          this.extractExpressionTokens(value[key]).forEach(addCandidate);
        }
      });
      ['target', 'Target', 'binding', 'Binding', 'field', 'Field', 'variable', 'Variable', 'tempVariable', 'TempVariable', 'expression', 'Expression', 'data', 'Data', 'meta', 'Meta', 'metadata', 'Metadata', 'props', 'Props'].forEach((key) => {
        if (value[key] !== undefined && value[key] !== null) {
          visit(value[key], depth + 1);
        }
      });
    };

    visit(rawValue, 0);
    return candidates;
  }

  mergeTargetCandidates(primary: string[], secondary: string[]) {
    const candidates: string[] = [];
    const seen: any = {};
    (primary || []).concat(secondary || []).forEach((candidate) => {
      const cleaned = this.toCleanString(candidate);
      if (cleaned && !seen[cleaned]) {
        seen[cleaned] = true;
        candidates.push(cleaned);
      }
    });
    return candidates;
  }

  extractExpressionTokens(value: any) {
    const text = this.toCleanString(value);
    if (!text) {
      return [];
    }
    const tokens: string[] = [];
    const patterns = [
      /\{\{\s*([A-Za-z0-9_.-]+)\s*\}\}/g,
      /@\{\s*([A-Za-z0-9_.-]+)\s*\}/g,
      /\$\{\s*([A-Za-z0-9_.-]+)\s*\}/g,
      /\b(?:TempVariables|tempVariables|Variables|variables|Fields|fields)\.([A-Za-z0-9_.-]+)\b/g,
    ];
    patterns.forEach((pattern) => {
      let match = pattern.exec(text);
      while (match) {
        if (match[1]) {
          tokens.push(match[1]);
          const parts = match[1].split('.');
          if (parts.length > 1) {
            tokens.push(parts[parts.length - 1]);
          }
        }
        match = pattern.exec(text);
      }
    });
    return tokens;
  }

  isLikelyWritableTargetName(candidate: any) {
    const value = this.toCleanString(candidate);
    if (!value || value === '[]' || value === '{}') {
      return false;
    }
    if (value.charAt(0) === '[' || value.charAt(0) === '{') {
      return false;
    }
    if (value.indexOf('":') !== -1 || value.indexOf('","') !== -1) {
      return false;
    }
    return value.length <= 160;
  }

  resolveParameterValue(value: any): any {
    if (value === undefined || value === null) {
      return '';
    }
    if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' || Array.isArray(value)) {
      return value;
    }
    if (typeof value === 'object') {
      const preferred = this.firstDefined([
        value.value,
        value.Value,
        value.data,
        value.Data,
        value.result,
        value.Result,
        value.key,
        value.Key,
        value.id,
        value.Id,
        value.fieldId,
        value.FieldId,
        value.fieldName,
        value.FieldName,
        value.label,
        value.Label,
        value.name,
        value.Name,
      ]);
      if (preferred !== undefined) {
        return this.resolveParameterValue(preferred);
      }
    }
    return value;
  }

  safeParseJson(value: any): any {
    if (Array.isArray(value) || (value && typeof value === 'object')) {
      return value;
    }
    const text = this.toCleanString(value);
    if (!text) {
      return null;
    }
    try {
      return JSON.parse(text);
    } catch (error) {
      return null;
    }
  }

  toCleanString(value: any): string {
    const resolved = this.resolveParameterValue(value);
    if (resolved === undefined || resolved === null) {
      return '';
    }
    if (typeof resolved === 'string' || typeof resolved === 'number' || typeof resolved === 'boolean') {
      return String(resolved).trim();
    }
    if (Array.isArray(resolved)) {
      return resolved.map((item) => this.toCleanString(item)).filter(Boolean).join(', ');
    }
    if (typeof resolved === 'object') {
      const preferred = this.firstDefined([
        resolved.value,
        resolved.Value,
        resolved.text,
        resolved.Text,
        resolved.display,
        resolved.Display,
        resolved.label,
        resolved.Label,
        resolved.name,
        resolved.Name,
        resolved.title,
        resolved.Title,
        resolved.key,
        resolved.Key,
      ]);
      if (preferred !== undefined) {
        return this.toCleanString(preferred);
      }
      try {
        return JSON.stringify(resolved);
      } catch (error) {
        return '';
      }
    }
    return '';
  }

  toBoolean(value: any, fallback: boolean) {
    if (value === true || value === false) {
      return value;
    }
    if (Array.isArray(value) && value.length > 0) {
      return this.toBoolean(value[0], fallback);
    }
    if (value && typeof value === 'object') {
      const objectValue = this.firstDefined([
        value.value,
        value.Value,
        value.key,
        value.Key,
        value.checked,
        value.Checked,
        value.selected,
        value.Selected,
        value.label,
        value.Label,
      ]);
      if (objectValue !== undefined) {
        return this.toBoolean(objectValue, fallback);
      }
    }
    const cleaned = this.toCleanString(value).toLowerCase();
    if (['true', '1', 'yes', 'y', 'on'].indexOf(cleaned) !== -1) {
      return true;
    }
    if (['false', '0', 'no', 'n', 'off'].indexOf(cleaned) !== -1) {
      return false;
    }
    return fallback;
  }

  normalizeDecisionCode(value: any) {
    const text = this.toCleanString(value).toLowerCase();
    if (!text) {
      return '';
    }
    if (text === 'request changes' || text === 'requestchanges' || text === 'request_change' || text === 'request-change' || text === 'revise' || text === 'revision') {
      return 'revise';
    }
    if (text === 'approved') {
      return 'approve';
    }
    if (text === 'rejected') {
      return 'reject';
    }
    return text.replace(/\s+/g, '-');
  }

  normalizeTone(value: any) {
    const text = this.toCleanString(value).toLowerCase();
    if (text === 'approve' || text === 'approved' || text === 'success' || text === 'green') {
      return 'approve';
    }
    if (text === 'reject' || text === 'rejected' || text === 'danger' || text === 'red') {
      return 'reject';
    }
    if (text === 'revise' || text === 'request-changes' || text === 'warning' || text === 'amber') {
      return 'revise';
    }
    if (text === 'escalate' || text === 'violet' || text === 'purple') {
      return 'escalate';
    }
    if (text === 'hold' || text === 'neutral' || text === 'gray' || text === 'grey') {
      return 'hold';
    }
    return 'neutral';
  }

  normalizePanelStyle(value: any) {
    const text = this.toCleanString(value).toLowerCase();
    if (text === 'compact' || text === 'bordered') {
      return text;
    }
    return 'standard';
  }

  toTitleLabel(value: string) {
    const text = this.toCleanString(value).replace(/[-_]+/g, ' ');
    return text.replace(/\w\S*/g, (word) => word.charAt(0).toUpperCase() + word.substr(1).toLowerCase());
  }

  firstDefined(values: any[]) {
    for (let index = 0; index < values.length; index += 1) {
      if (values[index] !== undefined && values[index] !== null) {
        return values[index];
      }
    }
    return undefined;
  }

  render() {
    const config = this.getConfig();
    const selectedOption = this.findOption(this.state.decision, config.options);
    const readonly = this.props.readonly === true;
    const panelClass = 'adp-panel adp-panel-' + config.panelStyle + (readonly ? ' adp-readonly' : '');
    return (
      <div className={panelClass}>
        <style>{this.getStyles()}</style>
        <div className="adp-header">
          <div>
            <div className="adp-title">{config.titleText}</div>
            {config.subtitleText ? <div className="adp-subtitle">{config.subtitleText}</div> : null}
          </div>
          {readonly ? <span className="adp-mode-badge">Readonly</span> : null}
        </div>

        {readonly ? (
          <div className="adp-readonly-box">
            {selectedOption ? (
              <div className={'adp-readonly-decision adp-tone-' + selectedOption.tone}>
                <span className="adp-readonly-dot" />
                <span>{selectedOption.label}</span>
              </div>
            ) : (
              <div className="adp-readonly-empty">{config.readonlyText}</div>
            )}
            {this.state.comment ? <div className="adp-readonly-comment">{this.state.comment}</div> : null}
          </div>
        ) : (
          <div>
            <div className="adp-options" role="group" aria-label="Approval decision">
              {config.options.map((option: DecisionOption) => this.renderDecisionButton(option, selectedOption))}
            </div>

            {config.showCommentBox ? (
              <div className="adp-comment-wrap">
                <textarea
                  className="adp-comment"
                  value={this.state.comment || ''}
                  placeholder={config.commentPlaceholder}
                  onChange={(event) => this.handleCommentChange(event)}
                />
              </div>
            ) : null}

            {this.state.validationMessage ? (
              <div className="adp-validation">{this.state.validationMessage}</div>
            ) : null}

            {config.showStatusSummary ? this.renderStatusSummary(selectedOption, config) : null}
          </div>
        )}
      </div>
    );
  }

  renderDecisionButton(option: DecisionOption, selectedOption: DecisionOption | null) {
    const isSelected = Boolean(selectedOption && selectedOption.code === option.code);
    const className = 'adp-option adp-tone-' + option.tone + (isSelected ? ' adp-option-selected' : '');
    return (
      <button
        type="button"
        className={className}
        key={option.code}
        onClick={() => this.handleDecisionClick(option.code)}
      >
        <span className="adp-option-main">
          <span className="adp-option-mark" />
          <span className="adp-option-label">{option.label}</span>
        </span>
        {option.helper ? <span className="adp-option-helper">{option.helper}</span> : null}
      </button>
    );
  }

  renderStatusSummary(selectedOption: DecisionOption | null, config: any) {
    if (!selectedOption) {
      return <div className="adp-summary">No decision selected yet.</div>;
    }
    const commentRequired = this.getValidationMessage(selectedOption.code, '', config) !== '';
    return (
      <div className="adp-summary">
        Selected: <strong>{selectedOption.label}</strong>
        {commentRequired ? <span> - comment required</span> : null}
      </div>
    );
  }

  getStyles() {
    return [
      '.adp-panel{box-sizing:border-box;width:100%;min-width:0;background:#fff;border:1px solid #e5eaf3;border-radius:8px;padding:18px;color:#111827;font-family:Inter,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;box-shadow:0 1px 2px rgba(16,24,40,.04);}',
      '.adp-panel *{box-sizing:border-box;}',
      '.adp-panel-compact{padding:14px;}',
      '.adp-panel-bordered{border-color:#cbd7ea;box-shadow:none;}',
      '.adp-readonly{background:#f8fafc;}',
      '.adp-header{display:flex;align-items:flex-start;justify-content:space-between;gap:14px;margin-bottom:14px;}',
      '.adp-title{font-size:16px;line-height:22px;font-weight:700;color:#101828;}',
      '.adp-subtitle{margin-top:3px;font-size:12px;line-height:18px;color:#667085;}',
      '.adp-mode-badge{flex:0 0 auto;font-size:12px;line-height:18px;color:#667085;background:#eef2f7;border:1px solid #d8deea;border-radius:999px;padding:3px 9px;}',
      '.adp-options{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;}',
      '.adp-option{appearance:none;border:1px solid #d8deea;background:#fff;border-radius:8px;padding:12px 12px;text-align:left;cursor:pointer;color:#344054;min-height:72px;transition:border-color .15s ease,background-color .15s ease,box-shadow .15s ease;}',
      '.adp-option:hover{border-color:#b8c7dc;background:#fbfcfe;}',
      '.adp-option-selected{box-shadow:0 0 0 3px rgba(20,111,246,.12);}',
      '.adp-option-main{display:flex;align-items:center;gap:8px;}',
      '.adp-option-mark{width:14px;height:14px;border-radius:999px;border:2px solid #b8c7dc;background:#fff;flex:0 0 auto;}',
      '.adp-option-selected .adp-option-mark{border-width:4px;}',
      '.adp-option-label{font-size:13px;line-height:18px;font-weight:700;color:#101828;}',
      '.adp-option-helper{display:block;margin-top:6px;font-size:12px;line-height:17px;color:#667085;}',
      '.adp-tone-approve.adp-option-selected{border-color:#12b76a;background:#f2fbf6;}',
      '.adp-tone-approve.adp-option-selected .adp-option-mark{border-color:#12b76a;}',
      '.adp-tone-reject.adp-option-selected{border-color:#e45665;background:#fff7f7;}',
      '.adp-tone-reject.adp-option-selected .adp-option-mark{border-color:#e45665;}',
      '.adp-tone-revise.adp-option-selected{border-color:#f5a623;background:#fffbf2;}',
      '.adp-tone-revise.adp-option-selected .adp-option-mark{border-color:#f5a623;}',
      '.adp-tone-escalate.adp-option-selected{border-color:#7a5af8;background:#f7f5ff;}',
      '.adp-tone-escalate.adp-option-selected .adp-option-mark{border-color:#7a5af8;}',
      '.adp-tone-hold.adp-option-selected,.adp-tone-neutral.adp-option-selected{border-color:#667085;background:#f8fafc;}',
      '.adp-tone-hold.adp-option-selected .adp-option-mark,.adp-tone-neutral.adp-option-selected .adp-option-mark{border-color:#667085;}',
      '.adp-comment-wrap{margin-top:12px;}',
      '.adp-comment{display:block;width:100%;min-height:92px;resize:vertical;border:1px solid #d8deea;border-radius:8px;padding:10px 12px;color:#101828;background:#fff;font-size:13px;line-height:20px;font-family:inherit;outline:none;}',
      '.adp-comment:focus{border-color:#146ff6;box-shadow:0 0 0 3px rgba(20,111,246,.12);}',
      '.adp-validation{margin-top:10px;border:1px solid #fecdca;background:#fff7f6;color:#b42318;border-radius:8px;padding:9px 10px;font-size:12px;line-height:18px;}',
      '.adp-summary{margin-top:10px;font-size:12px;line-height:18px;color:#667085;background:#f8fafc;border:1px solid #e5eaf3;border-radius:8px;padding:9px 10px;}',
      '.adp-summary strong{color:#101828;}',
      '.adp-readonly-box{border:1px solid #e5eaf3;border-radius:8px;background:#fff;padding:12px;}',
      '.adp-readonly-decision{display:inline-flex;align-items:center;gap:8px;border-radius:999px;padding:5px 10px;font-size:13px;line-height:18px;font-weight:700;background:#f8fafc;color:#344054;}',
      '.adp-readonly-dot{width:8px;height:8px;border-radius:999px;background:#667085;}',
      '.adp-readonly-decision.adp-tone-approve{background:#f2fbf6;color:#067647;}',
      '.adp-readonly-decision.adp-tone-approve .adp-readonly-dot{background:#12b76a;}',
      '.adp-readonly-decision.adp-tone-reject{background:#fff7f7;color:#b42318;}',
      '.adp-readonly-decision.adp-tone-reject .adp-readonly-dot{background:#e45665;}',
      '.adp-readonly-decision.adp-tone-revise{background:#fffbf2;color:#b54708;}',
      '.adp-readonly-decision.adp-tone-revise .adp-readonly-dot{background:#f5a623;}',
      '.adp-readonly-empty{font-size:13px;line-height:20px;color:#667085;}',
      '.adp-readonly-comment{margin-top:10px;padding-top:10px;border-top:1px solid #eef2f7;font-size:13px;line-height:20px;color:#344054;white-space:pre-wrap;}',
      '@media(max-width:640px){.adp-panel{padding:16px;}.adp-header{flex-direction:column;align-items:flex-start;}.adp-options{grid-template-columns:1fr;}.adp-option{min-height:auto;}}',
    ].join('');
  }
}
