/**不同类型 将有不同元数据 */

export enum ScriptSliceType {
  /** 文本 */
  text = "text",
  /**角色 */
  role = "role",
  /** 场景 */
  scene = "scene",
}

/**
 * 剧本片段值对象
 * @description 代表剧本中的一个片段，按值相等判断，不可变
 */
export class ScriptSlice {
  /**唯一标识符 - 仅作为局部唯一性标识，不作为全局Entity id */
  readonly id: string;
  /** 类型 */
  readonly type: ScriptSliceType;
  /** 剧本内容 */
  readonly text: string;
  /** 元数据 */
  metaData: any;

  constructor(
    id: string,
    type: ScriptSliceType,
    text: string,
    metaData: any = {}
  ) {
    this.id = id;
    this.type = type;
    this.text = text;
    this.metaData = metaData;
  }

  /**
   * 值对象相等性比较
   * @param other 另一个ScriptSlice实例
   * @returns 是否相等
   */
  equals(other: ScriptSlice): boolean {
    return (
      this.type === other.type &&
      this.text === other.text &&
      JSON.stringify(this.metaData) === JSON.stringify(other.metaData)
    );
  }
}

/**
 * 对话内容项值对象
 * @description 代表对话中的一个内容项，按值相等判断，不可变
 */
export interface ContentItem {
  /** 角色名称 */
  roleName: string;
  /** 对话内容 */
  content: string;
}

/**
 * 镜头值对象
 * @description 代表镜头信息，按值相等判断，不可变
 */
export class LensType {
   /**镜头名称 */
   readonly name: string;
   /**镜头描述 */
   readonly script: string;
   /**对话内容 */
   readonly content: ContentItem[];

  constructor(name: string, script: string, content: ContentItem[]) {
    this.name = name;
    this.script = script;
    this.content = content;
  }
}

/**
 * 标签实体接口
 */
export interface TagValueObject {
  /** 唯一标识 */
  readonly id: string;
  /** 标签名称 */
  name: string;
  /** 内容标签类型 */
  content: number | string;

  /** 颜色 */
  color?: string;
}


/**
 * 这是一个符合DDD概念的值对象，代表故事的核心细节。
 * 它封装了故事的四个关键元素：梗概、分类、主角和框架内容。
 * - 值对象是不可变的，一旦创建，其内部属性不会改变。
 * - 构造函数是私有的，强制使用工厂方法创建实例，以确保数据处理的封装性。
 */
export class StoryDetails {
  /** 故事梗概 */
  public synopsis: string = "";

  /** 故事分类标签 */
  public categories: string[] = [];

  /** 主角名称 */
  public protagonist: string = "";

  /** 激励事件 */
  public incitingIncident: string = "";

  /** 问题与新目标 */
  public problem: string = "";

  /** 冲突与障碍 */
  public conflict: string = "";

  /** 赌注 */
  public stakes: string = "";

  /** 人物弧线完成 */
  public characterArc: string = "";

  /**
   * 更新剧本
   * @param text 剧本文本
   */
  updateScript(text: string) {
    console.log('text.length', text.length)
    const scriptObject = this.createFromText(text);
    console.log('scriptObject', scriptObject)
    this.setProperties(scriptObject);
    return scriptObject;
  }
  setProperties(properties: {
    synopsis: string;
    categories: string[];
    protagonist: string;
    incitingIncident: string;
    problem: string;
    conflict: string;
    stakes: string;
    characterArc: string;
  }) {
    this.synopsis = properties.synopsis;
    this.categories = properties.categories;
    this.protagonist = properties.protagonist;
    this.incitingIncident = properties.incitingIncident;
    this.problem = properties.problem;
    this.conflict = properties.conflict;
    this.stakes = properties.stakes;
    this.characterArc = properties.characterArc;
  }
  constructor(text: string) {
    this.updateScript(text);
  }

  /**
   * 工厂方法：从原始英文文本中提取并创建 StoryDetails 值对象。
   * 该方法负责所有的解析逻辑，包括智能提取梗概和去除格式标签。
   * @param text 包含故事所有细节的原始Markdown或富文本。
   */
  public createFromText(text: string) {
   try {
     // --- 智能解析梗概 ---
    // 梗概通常位于“Core Elements”部分，描述主角和初始事件。
    // 我们提取“Protagonist”和“The Inciting Incident”的描述，并组合起来。
    const synopsis = this.extractContentByHeader(text, "Logline");
    const incitingIncidentText = this.extractContentByHeader(
      text,
      "The Inciting Incident",

    );
    // --- 其他字段的提取保持不变，使用标题进行查找 ---
    const categories = this.extractContentByHeader(text, "GENRE")
      .split(",")
      .map((s) => s.trim())
      .filter((s) => s.length > 0);
    const protagonist = this.extractContentByHeader(text, "Core Identity:");
    const problem = this.extractContentByHeader(text, "The Problem & New Goal");
    const conflict = this.extractContentByHeader(text, "Conflict & Obstacles");
    const stakes = this.extractContentByHeader(text, "The Stakes");
    const characterArc = this.extractContentByHeader(
      text,
      "Character Arc Accomplished"
    );

    return {
      synopsis,
      categories,
      protagonist,
      incitingIncident: incitingIncidentText,
      problem,
      conflict,
      stakes,
      characterArc,
    };
   } catch (error) {
    return {
      synopsis: "",
      categories: [],
      protagonist: "",
      incitingIncident: "",
      problem: "",
      conflict: "",
      stakes: "",
      characterArc: "",
    };
   }
  }
  /**
   * 辅助方法：根据标题提取其下的内容，并去除所有Markdown标签。
   * @param fullText 完整的源文本。
   * @param headerName 要提取内容所属的标题名（不区分大小写）。
   * @returns 提取出的纯文本内容。
   */
  public extractContentByHeader(
    fullText: string,
    headerName: string,
    debug: boolean = false
  ): string {
    try {
      debug && console.log(`正在查找标题: "${headerName}"`);

      // 转义正则表达式中的特殊字符
      const escapeRegex = (text: string): string => {
        return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
      };

      // 清理Markdown格式和多余的空白字符
      const cleanMarkdownContent = (content: string): string => {
        return (
          content
            // 移除markdown的粗体标记
            .replace(/\*\*([^*]+)\*\*/g, "$1")
            // 移除markdown的斜体标记
            .replace(/\*([^*]+)\*/g, "$1")
            // 移除列表标记（- 或 * 开头的行）
            .replace(/^\s*[-*]\s+/gm, "")
            // 移除代码块
            .replace(/```[\s\S]*?```/g, "")
            // 移除行内代码
            .replace(/`([^`]+)`/g, "$1")
            // 规范化空白字符：将多个连续空白（包括换行）替换为单个空格
            .replace(/\s+/g, " ")
            // 移除引号
            .replace(/["""'']/g, "")
            // 最终清理首尾空白
            .trim()
        );
      };

      // 标准化headerName，移除末尾的冒号和空格
      const normalizedHeaderName = headerName.replace(/:?\s*$/, "").trim();
      const escapedHeaderName = escapeRegex(normalizedHeaderName);

      let content = "";

     // 定义多种匹配模式，按优先级排序
const patterns = [
  // 1. 匹配带编号的主标题：数字. **标题:** 格式
  new RegExp(
    `\\d+\\.\\s*\\*\\*${escapedHeaderName}:?\\*\\*\\s*([\\s\\S]*?)(?=\\d+\\.\\s*\\*\\*[^*]+:?\\*\\*|\\*\\*[A-Z]+:\\*\\*|---|\$)`,
    "i"
  ),

  // 2. 匹配子标题：*标题:* 格式（在主标题下的子项）
  new RegExp(
    `\\*\\s*\\*\\*${escapedHeaderName}:?\\*\\*\\s*([\\s\\S]*?)(?=\\*\\s*\\*\\*[^*]+:?\\*\\*|\\d+\\.\\s*\\*\\*[^*]+:?\\*\\*|\\*\\*[A-Z]+:\\*\\*|---|\$)`,
    "i"
  ),

  // 3. 匹配独立的粗体标题：**标题:** 格式
  new RegExp(
    `^\\s*\\*\\*${escapedHeaderName}:?\\*\\*\\s*([\\s\\S]*?)(?=^\\s*\\*\\*[^*]+:?\\*\\*|\\d+\\.\\s*\\*\\*[^*]+:?\\*\\*|^\\s*\\*\\*[A-Z]+:\\*\\*|^---|\$)`,
    "im"
  ),

  // 4. 匹配简单的粗体键值对格式：**标题:** 值（适用于简单的键值对，如 **GENRE:** Drama）
  new RegExp(`\\*\\*${escapedHeaderName}:?\\*\\*\\s*([^\\n]+)`, "i"),

  // 5. 匹配简单的**标题:**格式（可能在段落中）
  new RegExp(
    `\\*\\*${escapedHeaderName}:?\\*\\*\\s*([\\s\\S]*?)(?=\\*\\*[^*]+:?\\*\\*|\\d+\\.\\s*\\*\\*[^*]+:?\\*\\*|\\n\\n|---|\$)`,
    "i"
  ),

  // 6. 匹配markdown标题：## 标题 格式
  new RegExp(
    `^#{1,6}\\s*${escapedHeaderName}:?\\s*\\n([\\s\\S]*?)(?=^#{1,6}\\s|^\\*\\*[^*]+:?\\*\\*|^---|\$)`,
    "im"
  ),

  // 7. 匹配冒号后的内容：标题: 内容（适用于简单的键值对格式）
  new RegExp(
    `^\\s*${escapedHeaderName}:?\\s*([\\s\\S]*?)(?=^\\s*[A-Za-z][^:]*:|^\\*\\*[^*]+:?\\*\\*|^---|\$)`,
    "im"
  ),
];

      // 尝试每种模式
      for (let i = 0; i < patterns.length; i++) {
        const regex = patterns[i];
        const match = fullText.match(regex);
        if (match && match[1] && match[1].trim()) {
          content = match[1].trim();
          debug && console.log(`使用模式 ${i + 1} 匹配成功`);
          debug &&
            console.log(`匹配的完整内容: "${match[0].substring(0, 150)}..."`);
          break;
        }
      }
      // console.log(headerName, content)
      if (!content) {
        debug && console.log(`没有找到标题 "${headerName}" 对应的内容`);
        // 尝试更宽松的匹配作为最后手段
        const loosePattern = new RegExp(
          `${escapedHeaderName}[:\\s]*([\\s\\S]{1,500}?)(?=\\n\\n|\\*\\*[^*]+\\*\\*|#{1,6}\\s|---|\$)`,
          "i"
        );
        const looseMatch = fullText.match(loosePattern);
        if (looseMatch && looseMatch[1]) {
          content = looseMatch[1].trim();
          debug && console.log("使用宽松匹配模式找到内容");
        } else {
          return "";
        }
      }

      debug && console.log(`原始匹配内容: "${content.substring(0, 100)}..."`);

      // 清理内容格式
      content = cleanMarkdownContent(content);

      // 如果内容太短，可能是匹配错误，返回空
      if (content.length < 20&&headerName!=='GENRE') {
        debug && console.log("匹配到的内容太短，可能匹配错误");
        return "";
      }

      debug && console.log(`清理后的内容: "${content.substring(0, 100)}..."`);
      return content;
    } catch (error) {
      console.log("内容提取出错:", error);
      return "";
    }
  }
  toObject() {
    return {
      synopsis: this.synopsis,
      categories: this.categories,
      protagonist: this.protagonist,
      incitingIncident: this.incitingIncident,
      problem: this.problem,
      conflict: this.conflict,
      stakes: this.stakes,
      characterArc: this.characterArc,
    };
  }
}

/**
 * 剧本聚合根
 * @description 作为聚合根，封装ScriptSlice的管理与行为
 */
export class ScriptValueObject {
  /** 剧本片段数组 - 值对象数组 */
  private readonly _scriptSlices: ScriptSlice[] = [];

  /**
   * 获取剧本片段数组的只读副本
   */
  get scriptSlices(): readonly ScriptSlice[] {
    return [...this._scriptSlices];
  }
  scriptText: string = "";
  storyDetails: StoryDetails;
  /**
   * @description: 构造函数，初始化剧本
   * @param scriptText 剧本文本字符串
   */
  constructor(scriptText?: string) {
    this.storyDetails = new StoryDetails("");
    if (scriptText) {
      this.parseFromString(scriptText);
    }
  }

  /**
   * @description: 从字符串解析剧本片段
   * @param scriptText 剧本文本字符串
   */
  parseFromString(scriptText: string) {
    this.scriptText += scriptText;
    return this.storyDetails.updateScript(this.scriptText);
  }
  /**
   * @description: 将剧本片段转换为字符串
   * @returns string
   */
  toString(): string {
    return this.scriptText;
  }
}

/**
 * 角色简单数据接口
 * @description 包含角色姓名、图片和描述
 */
export interface SimpleCharacter {
  /** 姓名 */
  name: string;
  /** 图片URL */
  imageUrl: string;
  /** 角色描述 */
  description: string;
}
