
### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Codes\XContentRepositoryService.cs

using System;
using AutoMapper;
using System.Linq;
using xModels.Dtos;
using System.Threading;
using xCommons.Extensions;
using xDataService.Providers;
using System.Threading.Tasks;
using System.Collections.Generic;
using xContentService.Interfaces;
using xContentService.Models.Dtos;
using xIdentityService.Interfaces;
using xIdentityService.Extensions;
using xContentService.Interfaces.Dtos;
using xContentService.Models.Entities;
using xContentService.Interfaces.Entities;

namespace xContentService.Providers.Dtos
{
    public class XContentRepositoryService : XBaseRepositoryService<XContent, XContentDto, Guid>, IXContentRepositoryService
    {
        private readonly IXIdentityProvider identityProvider;
        private readonly IXContentTitleResourceProvider titleResourceProvider;
        private readonly IXContentDescriptionResourceProvider descriptionResourceProvider;

        public XContentRepositoryService(
            IXContentRepository repository,
            IXIdentityProvider identityProvider,
            IXContentTitleResourceProvider titleResourceProvider,
            IXContentDescriptionResourceProvider descriptionResourceProvider,
            IEnumerable<Profile> mapperProfiles = null
        ) : base(
            repository,
            mapperProfiles
        )
        {
            this.identityProvider = identityProvider;
            this.titleResourceProvider = titleResourceProvider;
            this.descriptionResourceProvider = descriptionResourceProvider;
        }

        //
        #region Enrichers ...
        /// <summary>
        /// Enrich Specified Item ...
        /// </summary>
        /// <param name="source"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<XContentDto> Enrich(
            XContent source,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Mapping Default ...
            var result = Mapper.Map<XContentDto>(source);
            result = await Enrich(
                source: result,
                cancellationToken: cancellationToken
            );

            //
            return result;
        }

        /// <summary>
        /// Enrich Specified Item ...
        /// </summary>
        /// <param name="source"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<XContentDto> Enrich(
            XContentDto source,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Handdle Owner ...
            XPersonDto owner = null;
            if (!source.OwnerId.IsNullOrEmpty())
            {
                //
                // Try to Retrieve User Info ...
                var device = identityProvider.GetDevice();
                var userInfo = await identityProvider.GetUserInfo(
                    device: device,
                    userSelectByParam: source.OwnerId
                );
                owner = userInfo.ToXPersonDto();
            }

            //
            // Hanlde Content Title ...
            string contentTitle = string.Empty;
            XResourceDto titleResource = await titleResourceProvider.GetResource(
                identifier: source.Id,
                cancellationToken: cancellationToken
            );
            if (!titleResource.IsNullOrDefault())
            {
                contentTitle = titleResource.Resource;
            }

            //
            // Hanlde Content Description ...
            string contentDescription = string.Empty;
            XResourceDto descriptionResource = await descriptionResourceProvider.GetResource(
                identifier: source.Id,
                cancellationToken: cancellationToken
            );
            if (!descriptionResource.IsNullOrDefault())
            {
                contentDescription = descriptionResource.Resource;
            }

            //
            // Attach Extracted Items ...
            var result = Mapper.Map<XContentDto>(source, options =>
            {
                //
                options.Items[nameof(source.Owner)] = owner;
                options.Items[nameof(source.Title)] = contentTitle;
                options.Items[nameof(source.TitleResource)] = titleResource;
                options.Items[nameof(source.Description)] = contentDescription;
                options.Items[nameof(source.DescriptionResource)] = descriptionResource;
            });

            //
            return result;
        }

        /// <summary>
        /// Enrich Specified Items ...
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<IEnumerable<XContentDto>> EnrichRange(
            IEnumerable<XContent> sources, 
            CancellationToken cancellationToken = default
        )
        {
            //
            var tasks = sources
                .Select(x => Enrich(x, cancellationToken));

            //
            var result = await Task.WhenAll(tasks);
            return result;
        }

        /// <summary>
        /// Enrich Specified Items ...
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<IEnumerable<XContentDto>> EnrichRange(
            IEnumerable<XContentDto> sources, 
            CancellationToken cancellationToken = default
        )
        {
            //
            var tasks = sources
                .Select(x => Enrich(x, cancellationToken));

            //
            var result = await Task.WhenAll(tasks);
            return result;
        }
        #endregion
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Markdown\AppexChartOptionsDocumentation.html

<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>مستند جامع تحلیل ApexOptions در ApexCharts</title>
    <link href="https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@v33.003/Vazirmatn-font-face.css" rel="stylesheet" type="text/css" />
    <style>
        :root {
            --primary-color: #2c3e50;
            --secondary-color: #3498db;
            --accent-color: #e74c3c;
            --success-color: #27ae60;
            --bg-color: #f8f9fa;
            --text-color: #333;
            --code-bg: #282c34;
            --code-text: #abb2bf;
            --border-color: #e0e0e0;
        }

        body {
            font-family: 'Vazirmatn', Tahoma, sans-serif;
            line-height: 1.8;
            color: var(--text-color);
            background-color: #fff;
            margin: 0;
            padding: 0;
        }

        .container {
            max-width: 950px;
            margin: 0 auto;
            padding: 40px 20px;
        }

        /* Header Styles */
        .header {
            border-bottom: 3px solid var(--secondary-color);
            padding-bottom: 20px;
            margin-bottom: 30px;
            display: flex;
            justify-content: space-between;
            align-items: center;
            flex-wrap: wrap;
            gap: 20px;
        }

        .header-info h1 {
            color: var(--primary-color);
            font-size: 24px;
            margin: 0 0 10px 0;
        }

        .header-info p {
            margin: 5px 0;
            font-size: 14px;
            color: #555;
        }

        .company-badge {
            background-color: var(--primary-color);
            color: white;
            padding: 10px 20px;
            border-radius: 8px;
            text-align: center;
            font-weight: bold;
        }

        .company-badge a {
            color: #fff;
            text-decoration: none;
            font-size: 14px;
        }

        /* Content Styles */
        h2 {
            color: var(--secondary-color);
            border-right: 4px solid var(--secondary-color);
            padding-right: 15px;
            margin-top: 40px;
            font-size: 20px;
            page-break-after: avoid;
        }

        h3 {
            color: var(--primary-color);
            font-size: 18px;
            margin-top: 25px;
            page-break-after: avoid;
        }

        h4 {
            color: var(--success-color);
            font-size: 16px;
            margin-top: 20px;
            page-break-after: avoid;
        }

        p, li {
            font-size: 15px;
        }

        ul, ol {
            padding-right: 25px;
        }

        li {
            margin-bottom: 8px;
        }

        /* Table Styles */
        table {
            width: 100%;
            border-collapse: collapse;
            margin: 20px 0;
            font-size: 14px;
            page-break-inside: avoid;
        }

        th, td {
            border: 1px solid var(--border-color);
            padding: 10px;
            text-align: right;
        }

        th {
            background-color: var(--secondary-color);
            color: white;
            font-weight: bold;
        }

        tr:nth-child(even) {
            background-color: #f9f9f9;
        }

        /* Code Block Styles */
        pre {
            background-color: var(--code-bg);
            color: var(--code-text);
            padding: 15px;
            border-radius: 6px;
            overflow-x: auto;
            font-family: 'Consolas', 'Monaco', monospace;
            font-size: 13px;
            line-height: 1.5;
            direction: ltr;
            text-align: left;
            page-break-inside: avoid;
        }

        code {
            font-family: 'Consolas', 'Monaco', monospace;
        }

        p code {
            background-color: #eee;
            color: #c7254e;
            padding: 2px 6px;
            border-radius: 4px;
            font-size: 13px;
            direction: ltr;
            display: inline-block;
        }

        /* Info Box */
        .info-box {
            background-color: #e8f4fd;
            border-right: 4px solid var(--secondary-color);
            padding: 15px;
            margin: 15px 0;
            border-radius: 4px;
        }

        .warning-box {
            background-color: #fff3cd;
            border-right: 4px solid #ffc107;
            padding: 15px;
            margin: 15px 0;
            border-radius: 4px;
        }

        /* Print Button */
        .print-btn {
            position: fixed;
            bottom: 30px;
            left: 30px;
            background-color: var(--secondary-color);
            color: white;
            border: none;
            padding: 12px 24px;
            border-radius: 50px;
            font-family: 'Vazirmatn', sans-serif;
            font-size: 16px;
            cursor: pointer;
            box-shadow: 0 4px 6px rgba(0,0,0,0.1);
            transition: background 0.3s;
            z-index: 1000;
        }

        .print-btn:hover {
            background-color: #2980b9;
        }

        /* Print Specific Styles */
        @media print {
            .no-print {
                display: none !important;
            }
            body {
                background-color: #fff;
                font-size: 11pt;
            }
            .container {
                max-width: 100%;
                padding: 0;
            }
            .header {
                border-bottom: 2px solid #000;
            }
            h2 {
                color: #000;
                border-right-color: #000;
            }
            h3, h4 {
                color: #000;
            }
            pre {
                background-color: #f4f4f4;
                color: #000;
                border: 1px solid #ddd;
                white-space: pre-wrap;
                word-wrap: break-word;
                font-size: 10pt;
            }
            p code {
                background-color: #f0f0f0;
                color: #000;
                border: 1px solid #ccc;
            }
            a {
                text-decoration: none;
                color: #000;
            }
            table {
                font-size: 10pt;
            }
            th {
                background-color: #ddd;
                color: #000;
            }
            .info-box, .warning-box {
                border: 1px solid #ccc;
            }
        }
    </style>
</head>
<body>

    <button class="print-btn no-print" onclick="window.print()">🖨️ ذخیره به عنوان PDF / چاپ</button>

    <div class="container">
        <!-- Header Section -->
        <div class="header">
            <div class="header-info">
                <h1>مستند جامع تحلیل ApexOptions در ApexCharts</h1>
                <p>📅 تاریخ تهیه: دوشنبه ۱۱ شهریور ۱۴۰۴</p>
                <p>📌 موضوع: بررسی تفصیلی فیلدهای ApexOptions</p>
            </div>
            <div class="company-badge">
                شرکت فن آوران ساحر علم<br>
                <a href="https://saherelm.ir" target="_blank">https://saherelm.ir</a><br>
                <span style="font-size: 12px; font-weight: normal; margin-top: 5px; display: block;">توسعه‌دهنده: هادی خزاعی اصل</span>
            </div>
        </div>

        <!-- Introduction -->
        <h2>۱. مقدمه</h2>
        <p>
            مدل <code>ApexOptions</code> قلب تپنده کتابخانه ApexCharts است و تمام تنظیمات نمودار را در بر می‌گیرد. این مدل شامل <strong>۲۵ فیلد اصلی</strong> است که هر کدام بخش خاصی از نمودار را کنترل می‌کنند. در این سند، هر فیلد به همراه زیرفیلدها، توضیحات و مثال‌های کاربردی بررسی شده است.
        </p>

        <div class="info-box">
            <strong>📌 نکته:</strong> تمام فیلدها اختیاری هستند و در صورت عدم تعریف، از مقادیر پیش‌فرض ApexCharts استفاده می‌شود.
        </div>

        <!-- Overview Table -->
        <h2>۲. جدول کلی فیلدهای ApexOptions</h2>
        <table>
            <thead>
                <tr>
                    <th>ردیف</th>
                    <th>نام فیلد</th>
                    <th>توضیح</th>
                </tr>
            </thead>
            <tbody>
                <tr><td>۱</td><td><code>annotations</code></td><td>حاشیه‌نویسی‌ها (خطوط، نقاط، متن‌ها)</td></tr>
                <tr><td>۲</td><td><code>chart</code></td><td>تنظیمات اصلی نمودار (نوع، ابعاد، انیمیشن)</td></tr>
                <tr><td>۳</td><td><code>colors</code></td><td>رنگ‌های سری‌های داده</td></tr>
                <tr><td>۴</td><td><code>dataLabels</code></td><td>برچسب‌های داده روی نقاط</td></tr>
                <tr><td>۵</td><td><code>drilldown</code></td><td>ناوبری عمیق (Drilldown)</td></tr>
                <tr><td>۶</td><td><code>fill</code></td><td>پرکننده (رنگ، گرادیانت، الگو)</td></tr>
                <tr><td>۷</td><td><code>forecastDataPoints</code></td><td>نقاط داده پیش‌بینی</td></tr>
                <tr><td>۸</td><td><code>grid</code></td><td>شبکه‌بندی نمودار</td></tr>
                <tr><td>۹</td><td><code>labels</code></td><td>برچسب‌های نمودار</td></tr>
                <tr><td>۱۰</td><td><code>legend</code></td><td>راهنمای نمودار</td></tr>
                <tr><td>۱۱</td><td><code>markers</code></td><td>نشانگرهای نقاط</td></tr>
                <tr><td>۱۲</td><td><code>noData</code></td><td>پیام عدم وجود داده</td></tr>
                <tr><td>۱۳</td><td><code>plugins</code></td><td>افزونه‌ها (Weave)</td></tr>
                <tr><td>۱۴</td><td><code>plotOptions</code></td><td>گزینه‌های خاص هر نوع نمودار</td></tr>
                <tr><td>۱۵</td><td><code>responsive</code></td><td>تنظیمات واکنش‌گرا</td></tr>
                <tr><td>۱۶</td><td><code>series</code></td><td>سری‌های داده</td></tr>
                <tr><td>۱۷</td><td><code>states</code></td><td>حالت‌های مختلف (hover, active)</td></tr>
                <tr><td>۱۸</td><td><code>stroke</code></td><td>خطوط نمودار</td></tr>
                <tr><td>۱۹</td><td><code>subtitle</code></td><td>زیرعنوان</td></tr>
                <tr><td>۲۰</td><td><code>theme</code></td><td>تم (روشن/تاریک)</td></tr>
                <tr><td>۲۱</td><td><code>title</code></td><td>عنوان نمودار</td></tr>
                <tr><td>۲۲</td><td><code>tooltip</code></td><td>Tooltip (راهنمای شناور)</td></tr>
                <tr><td>۲۳</td><td><code>xaxis</code></td><td>محور X</td></tr>
                <tr><td>۲۴</td><td><code>yaxis</code></td><td>محور Y</td></tr>
            </tbody>
        </table>

        <!-- Detailed Analysis -->
        <h2>۳. بررسی تفصیلی فیلدها</h2>

        <!-- annotations -->
        <h3>۳.۱. فیلد <code>annotations</code> - حاشیه‌نویسی‌ها</h3>
        <p>این فیلد برای اضافه کردن حاشیه‌نویسی‌های سفارشی (خطوط، نقاط، متن‌ها) به نمودار استفاده می‌شود.</p>
        
        <h4>زیرفیلدها:</h4>
        <ul>
            <li><code>xaxis</code>: حاشیه‌نویسی‌های محور X (خطوط عمودی)</li>
            <li><code>yaxis</code>: حاشیه‌نویسی‌های محور Y (خطوط افقی)</li>
            <li><code>points</code>: حاشیه‌نویسی‌های نقطه‌ای</li>
            <li><code>texts</code>: متن‌های سفارشی</li>
            <li><code>images</code>: تصاویر سفارشی</li>
        </ul>

        <h4>ساختار XAxisAnnotations:</h4>
        <pre><code>xaxis?: [
  {
    id?: number | string;
    x?: null | number | string;
    x2?: null | number | string;
    strokeDashArray?: number;
    fillColor?: string;
    borderColor?: string;
    borderWidth?: number;
    opacity?: number;
    offsetX?: number;
    offsetY?: number;
    label?: {
      text?: string | string[];
      textAnchor?: string;
      offsetX?: number;
      offsetY?: number;
      style?: {
        background?: string;
        color?: string;
        fontSize?: string;
        fontFamily?: string;
        fontWeight?: string | number;
        padding?: { left?: number; right?: number; top?: number; bottom?: number; };
      };
    };
  }
]</code></pre>

        <h4>مثال کاربردی:</h4>
        <pre><code>annotations: {
  xaxis: [{
    x: new Date('2024-06-01').getTime(),
    borderColor: '#FF0000',
    label: {
      borderColor: '#FF0000',
      style: { color: '#fff', background: '#FF0000' },
      text: 'تاریخ مهم'
    }
  }],
  yaxis: [{
    y: 50,
    borderColor: '#00FF00',
    label: { text: 'خط هدف', style: { color: '#00FF00' } }
  }],
  points: [{
    x: 'فروردین',
    y: 50,
    marker: { size: 8, fillColor: '#FF0000' },
    label: { text: 'نقطه مهم' }
  }]
}</code></pre>

        <!-- chart -->
        <h3>۳.۲. فیلد <code>chart</code> - تنظیمات اصلی نمودار</h3>
        <p>این فیلد مهم‌ترین بخش تنظیمات است و رفتار کلی نمودار را کنترل می‌کند.</p>

        <h4>زیرفیلدهای اصلی:</h4>
        <ul>
            <li><code>width</code>: عرض نمودار (عدد یا درصد)</li>
            <li><code>height</code>: ارتفاع نمودار (عدد یا درصد)</li>
            <li><code>type</code>: نوع نمودار (line, area, bar, pie, donut, radialBar, scatter, bubble, heatmap, candlestick, boxPlot, violin, histogram, radar, polarArea, rangeBar, rangeArea, treemap, waffle, sunburst, funnel, pyramid, gauge)</li>
            <li><code>foreColor</code>: رنگ متن پیش‌زمینه</li>
            <li><code>fontFamily</code>: فونت متن</li>
            <li><code>background</code>: رنگ پس‌زمینه</li>
            <li><code>animations</code>: تنظیمات انیمیشن</li>
            <li><code>events</code>: رویدادهای نمودار</li>
            <li><code>toolbar</code>: نوار ابزار</li>
            <li><code>zoom</code>: تنظیمات زوم</li>
            <li><code>selection</code>: تنظیمات انتخاب</li>
            <li><code>stacked</code>: نمودار پشته‌ای</li>
            <li><code>stackType</code>: نوع پشته (normal, 100%)</li>
        </ul>

        <h4>ساختار chart:</h4>
        <pre><code>chart: {
  width?: string | number;
  height?: string | number;
  type?: 'line' | 'area' | 'bar' | 'pie' | 'donut' | 'radialBar' | 
         'scatter' | 'bubble' | 'heatmap' | 'candlestick' | 
         'boxPlot' | 'violin' | 'histogram' | 'radar' | 
         'polarArea' | 'rangeBar' | 'rangeArea' | 'treemap' | 
         'waffle' | 'sunburst' | 'funnel' | 'pyramid' | 'gauge';
  foreColor?: string;
  fontFamily?: string;
  background?: string;
  offsetX?: number;
  offsetY?: number;
  dropShadow?: {
    enabled?: boolean;
    top?: number;
    left?: number;
    blur?: number;
    opacity?: number;
    color?: string | string[];
  };
  events?: {
    animationEnd?(chart: ApexCharts, options?: any): void;
    beforeMount?(chart: ApexCharts, options?: any): void;
    mounted?(chart: ApexCharts, options?: any): void;
    updated?(chart: ApexCharts, options?: any): void;
    mouseMove?(e: MouseEvent, chart?: ApexCharts, options?: any): void;
    mouseLeave?(e: MouseEvent, chart?: ApexCharts, options?: any): void;
    click?(e: MouseEvent, chart?: ApexCharts, options?: any): void;
    legendClick?(chart: ApexCharts, seriesIndex?: number, options?: any): void;
    markerClick?(e: MouseEvent, chart?: ApexCharts, options?: any): void;
    selection?(chart: ApexCharts, options?: any): void;
    dataPointSelection?(e: MouseEvent, chart?: ApexCharts, options?: any): void;
    dataPointMouseEnter?(e: MouseEvent, chart?: ApexCharts, options?: any): void;
    dataPointMouseLeave?(e: MouseEvent, chart?: ApexCharts, options?: any): void;
    beforeZoom?(chart: ApexCharts, options?: any): boolean | void;
    beforeResetZoom?(chart: ApexCharts, options?: any): boolean | void;
    zoomed?(chart: ApexCharts, options?: any): void;
    scrolled?(chart: ApexCharts, options?: any): void;
    brushScrolled?(chart: ApexCharts, options?: any): void;
  };
  animations?: {
    enabled?: boolean;
    easing?: 'linear' | 'easeInSine' | 'easeOutSine' | 'easeInOutSine' | 
             'easeInQuad' | 'easeOutQuad' | 'easeInOutQuad' | 
             'easeInCubic' | 'easeOutCubic' | 'easeInOutCubic' | string | 
             [number, number, number, number] | ((t: number) => number);
    speed?: number;
    animateGradually?: { enabled?: boolean; delay?: number; };
    dynamicAnimation?: { enabled?: boolean; speed?: number; easing?: string; };
  };
  toolbar?: {
    show?: boolean;
    offsetX?: number;
    offsetY?: number;
    tools?: {
      download?: boolean | string;
      selection?: boolean | string;
      zoom?: boolean | string;
      zoomin?: boolean | string;
      zoomout?: boolean | string;
      pan?: boolean | string;
      reset?: boolean | string;
      measure?: boolean | string;
      customIcons?: Array&lt;{
        icon?: string;
        title?: string;
        index?: number;
        class?: string;
        click?(chart: ApexCharts, options?: any, e?: MouseEvent): void;
      }&gt;;
    };
    export?: {
      csv?: { filename?: string; columnDelimiter?: string; headerCategory?: string; headerValue?: string; };
      svg?: { filename?: string; };
      png?: { filename?: string; };
    };
    autoSelected?: 'zoom' | 'selection' | 'pan' | 'measure';
  };
  zoom?: {
    enabled?: boolean;
    type?: 'x' | 'y' | 'xy';
    autoScaleYaxis?: boolean;
    allowMouseWheelZoom?: boolean | 'auto';
    pinch?: boolean | 'auto';
    zoomedArea?: {
      fill?: { color?: string; opacity?: number; };
      stroke?: { color?: string; opacity?: number; width?: number; };
    };
  };
  selection?: {
    enabled?: boolean;
    type?: string;
    fill?: { color?: string; opacity?: number; };
    stroke?: { width?: number; color?: string; opacity?: number; dashArray?: number; };
    xaxis?: { min?: number; max?: number; };
    yaxis?: { min?: number; max?: number; };
  };
  stacked?: boolean;
  stackType?: 'normal' | '100%';
  id?: string;
  group?: string;
  locales?: Array&lt;{ name?: string; options?: any; }&gt;;
  defaultLocale?: string;
  parentHeightOffset?: number;
  redrawOnParentResize?: boolean;
  redrawOnWindowResize?: boolean | ((...args: any[]) => boolean);
  sparkline?: { enabled?: boolean; };
}</code></pre>

        <h4>مثال کاربردی:</h4>
        <pre><code>chart: {
  type: 'line',
  height: 350,
  foreColor: '#333',
  fontFamily: 'Vazirmatn, sans-serif',
  animations: {
    enabled: true,
    easing: 'easeInOutCubic',
    speed: 800,
    animateGradually: { enabled: true, delay: 150 },
    dynamicAnimation: { enabled: true, speed: 350 }
  },
  toolbar: {
    show: true,
    tools: {
      download: true,
      zoom: true,
      zoomin: true,
      zoomout: true,
      reset: true,
      customIcons: [{
        icon: '&lt;svg&gt;...&lt;/svg&gt;',
        title: 'دکمه سفارشی',
        index: 0,
        click: (chart, options, e) => { console.log('کلیک شد'); }
      }]
    }
  },
  zoom: {
    enabled: true,
    type: 'x',
    autoScaleYaxis: false
  },
  events: {
    mounted: (chart, options) => { console.log('نمودار نصب شد'); },
    dataPointSelection: (e, chart, options) => { console.log('نقطه انتخاب شد'); }
  }
}</code></pre>

        <!-- colors -->
        <h3>۳.۳. فیلد <code>colors</code> - رنگ‌های سری‌های داده</h3>
        <p>آرایه‌ای از رنگ‌ها برای سری‌های داده. هر سری با یک رنگ متفاوت نمایش داده می‌شود.</p>

        <h4>ساختار:</h4>
        <pre><code>colors?: Array&lt;string | ((opts: {
  value: number;
  seriesIndex: number;
  dataPointIndex: number;
  w: any;
}) => string)&gt;</code></pre>

        <h4>مثال کاربردی:</h4>
        <pre><code>// رنگ‌های ثابت
colors: ['#008FFB', '#00E396', '#FEB019', '#FF4560', '#775DD0']

// رنگ‌های داینامیک بر اساس مقدار
colors: [
  function({ value, seriesIndex, dataPointIndex }) {
    return value > 50 ? '#00E396' : '#FF4560';
  }
]</code></pre>

        <!-- dataLabels -->
        <h3>۳.۴. فیلد <code>dataLabels</code> - برچسب‌های داده</h3>
        <p>برچسب‌هایی که روی نقاط داده نمایش داده می‌شوند.</p>

        <h4>ساختار:</h4>
        <pre><code>dataLabels?: {
  enabled?: boolean;
  enabledOnSeries?: number[];
  formatter?(val: string | number | number[], opts?: any): string | number | (string | number)[];
  textAnchor?: 'start' | 'middle' | 'end';
  distributed?: boolean;
  offsetX?: number;
  offsetY?: number;
  style?: {
    fontSize?: string;
    fontFamily?: string;
    fontWeight?: string | number;
    colors?: string[];
  };
  background?: {
    enabled?: boolean;
    foreColor?: string;
    borderRadius?: number;
    padding?: number;
    opacity?: number;
    borderWidth?: number;
    borderColor?: string;
    dropShadow?: { enabled?: boolean; top?: number; left?: number; blur?: number; opacity?: number; color?: string | string[]; };
  };
  dropShadow?: { enabled?: boolean; top?: number; left?: number; blur?: number; opacity?: number; color?: string | string[]; };
}</code></pre>

        <h4>مثال کاربردی:</h4>
        <pre><code>dataLabels: {
  enabled: true,
  formatter: (val) => val + '%',
  style: {
    fontSize: '12px',
    fontFamily: 'Vazirmatn',
    colors: ['#fff']
  },
  background: {
    enabled: true,
    foreColor: '#fff',
    borderRadius: 4,
    padding: 4,
    borderColor: '#000',
    borderWidth: 1
  }
}</code></pre>

        <!-- fill -->
        <h3>۳.۵. فیلد <code>fill</code> - پرکننده</h3>
        <p>تنظیمات پرکننده (رنگ، گرادیانت، الگو، تصویر).</p>

        <h4>ساختار:</h4>
        <pre><code>fill?: {
  colors?: string[];
  opacity?: number | number[];
  type?: string | string[];
  gradient?: {
    shade?: string;
    type?: string;
    shadeIntensity?: number;
    gradientToColors?: string[];
    inverseColors?: boolean;
    opacityFrom?: number | number[];
    opacityTo?: number | number[];
    stops?: number[];
    colorStops?: Array&lt;{ offset: number; color: string; opacity: number; }&gt; | Array&lt;Array&lt;{ offset: number; color: string; opacity: number; }&gt;&gt;;
  };
  image?: {
    src?: string | string[];
    width?: number;
    height?: number;
  };
  pattern?: {
    style?: string | string[];
    width?: number;
    height?: number;
    strokeWidth?: number;
  };
}</code></pre>

        <h4>مثال کاربردی:</h4>
        <pre><code>fill: {
  type: 'gradient',
  gradient: {
    shade: 'light',
    type: 'vertical',
    shadeIntensity: 0.5,
    gradientToColors: ['#008FFB', '#00E396'],
    opacityFrom: 1,
    opacityTo: 0.5,
    stops: [0, 50, 100]
  }
}</code></pre>

        <!-- grid -->
        <h3>۳.۶. فیلد <code>grid</code> - شبکه‌بندی</h3>
        <p>تنظیمات شبکه‌بندی نمودار.</p>

        <h4>ساختار:</h4>
        <pre><code>grid?: {
  show?: boolean;
  borderColor?: string;
  strokeDashArray?: number;
  position?: 'front' | 'back';
  xaxis?: { lines?: { show?: boolean; offsetX?: number; offsetY?: number; }; };
  yaxis?: { lines?: { show?: boolean; offsetX?: number; offsetY?: number; }; };
  row?: { colors?: string[]; opacity?: number; };
  column?: { colors?: string[]; opacity?: number; };
  padding?: { top?: number; right?: number; bottom?: number; left?: number; };
}</code></pre>

        <h4>مثال کاربردی:</h4>
        <pre><code>grid: {
  show: true,
  borderColor: '#e0e0e0',
  strokeDashArray: 4,
  position: 'back',
  row: { colors: ['#f3f3f3', 'transparent'], opacity: 0.5 },
  padding: { top: 0, right: 10, bottom: 0, left: 10 }
}</code></pre>

        <!-- legend -->
        <h3>۳.۷. فیلد <code>legend</code> - راهنمای نمودار</h3>
        <p>تنظیمات راهنمای نمودار (Legend).</p>

        <h4>ساختار:</h4>
        <pre><code>legend?: {
  show?: boolean;
  showForSingleSeries?: boolean;
  showForNullSeries?: boolean;
  showForZeroSeries?: boolean;
  floating?: boolean;
  inverseOrder?: boolean;
  position?: 'top' | 'right' | 'bottom' | 'left';
  horizontalAlign?: 'left' | 'center' | 'right';
  fontSize?: string;
  fontFamily?: string;
  fontWeight?: string | number;
  width?: number;
  height?: number;
  formatter?(legendName: string, opts?: { seriesIndex: number; w: any; }): string;
  tooltipHoverFormatter?(legendName: string, opts?: { seriesIndex: number; w: any; }): string;
  customLegendItems?: string[];
  labels?: { colors?: string | string[]; useSeriesColors?: boolean; };
  markers?: {
    width?: number;
    height?: number;
    strokeColor?: string;
    strokeWidth?: number;
    radius?: number;
    customHTML?(): any;
    onClick?(): void;
    offsetX?: number;
    offsetY?: number;
  };
  itemMargin?: { horizontal?: number; vertical?: number; };
  onItemClick?: { toggleDataSeries?: boolean; };
  onItemHover?: { highlightDataSeries?: boolean; };
}</code></pre>

        <h4>مثال کاربردی:</h4>
        <pre><code>legend: {
  show: true,
  position: 'top',
  horizontalAlign: 'center',
  fontSize: '14px',
  fontFamily: 'Vazirmatn',
  markers: { width: 12, height: 12, radius: 6 },
  onItemClick: { toggleDataSeries: true },
  onItemHover: { highlightDataSeries: true }
}</code></pre>

        <!-- markers -->
        <h3>۳.۸. فیلد <code>markers</code> - نشانگرها</h3>
        <p>تنظیمات نشانگرهای نقاط داده.</p>

        <h4>ساختار:</h4>
        <pre><code>markers?: {
  size?: number | number[];
  colors?: string | string[];
  strokeColors?: string | string[];
  strokeWidth?: number | number[];
  strokeOpacity?: number | number[];
  strokeDashArray?: number | number[];
  fillOpacity?: number | number[];
  discrete?: Array&lt;{
    seriesIndex?: number;
    dataPointIndex?: number;
    fillColor?: string;
    strokeColor?: string;
    size?: number;
  }&gt;;
  shape?: 'circle' | 'square' | 'rect';
  offsetX?: number;
  offsetY?: number;
  hover?: { size?: number; sizeOffset?: number; };
  onClick?(e?: any): void;
  onDblClick?(e?: any): void;
  showNullDataPoints?: boolean;
}</code></pre>

        <h4>مثال کاربردی:</h4>
        <pre><code>markers: {
  size: 6,
  colors: ['#008FFB'],
  strokeColors: '#fff',
  strokeWidth: 2,
  hover: { size: 8, sizeOffset: 3 },
  discrete: [{
    seriesIndex: 0,
    dataPointIndex: 3,
    fillColor: '#FF0000',
    size: 10
  }]
}</code></pre>

        <!-- plotOptions -->
        <h3>۳.۹. فیلد <code>plotOptions</code> - گزینه‌های خاص هر نوع نمودار</h3>
        <p>این فیلد تنظیمات خاص هر نوع نمودار را شامل می‌شود.</p>

        <h4>ساختار (خلاصه):</h4>
        <pre><code>plotOptions?: {
  bar?: {
    horizontal?: boolean;
    columnWidth?: string | number;
    barHeight?: string | number;
    distributed?: boolean;
    borderRadius?: number;
    borderRadiusApplication?: 'around' | 'end';
    borderRadiusWhenStacked?: 'all' | 'last';
    hideZeroBarsWhenGrouped?: boolean;
    rangeBarOverlap?: boolean;
    rangeBarGroupRows?: boolean;
    isDumbbell?: boolean;
    dumbbellColors?: string[][];
    colors?: {
      ranges?: Array&lt;{ from?: number; to?: number; color?: string; }&gt;;
      backgroundBarColors?: string[];
      backgroundBarOpacity?: number;
      backgroundBarRadius?: number;
    };
    dataLabels?: {
      maxItems?: number;
      hideOverflowingLabels?: boolean;
      position?: string;
      orientation?: 'horizontal' | 'vertical';
      total?: {
        enabled?: boolean;
        formatter?(val?: string, opts?: any): string;
        offsetX?: number;
        offsetY?: number;
        style?: { color?: string; fontSize?: string; fontFamily?: string; fontWeight?: string | number; };
      };
    };
  };
  bubble?: { minBubbleRadius?: number; maxBubbleRadius?: number; };
  candlestick?: {
    colors?: { upward?: string | string[]; downward?: string | string[]; };
    wick?: { useFillColor?: boolean; };
  };
  boxPlot?: {
    colors?: { upper?: string | string[]; lower?: string | string[]; };
  };
  heatmap?: {
    radius?: number;
    enableShades?: boolean;
    shadeIntensity?: number;
    reverseNegativeShade?: boolean;
    distributed?: boolean;
    useFillColorAsStroke?: boolean;
    colorScale?: {
      ranges?: Array&lt;{ from?: number; to?: number; color?: string; foreColor?: string; name?: string; }&gt;;
      inverse?: boolean;
      min?: number;
      max?: number;
    };
  };
  treemap?: {
    enableShades?: boolean;
    shadeIntensity?: number;
    distributed?: boolean;
    reverseNegativeShade?: boolean;
    useFillColorAsStroke?: boolean;
    dataLabels?: { format?: 'scale' | 'truncate'; };
    borderRadius?: number;
    colorScale?: {
      inverse?: boolean;
      ranges?: Array&lt;{ from?: number; to?: number; color?: string; foreColor?: string; name?: string; }&gt;;
      min?: number;
      max?: number;
    };
  };
  pie?: {
    startAngle?: number;
    endAngle?: number;
    customScale?: number;
    offsetX?: number;
    offsetY?: number;
    expandOnClick?: boolean;
    expandOffset?: number;
    dataLabels?: { offset?: number; };
  };
  donut?: {
    size?: string;
    background?: string;
    labels?: {
      show?: boolean;
      name?: { show?: boolean; fontSize?: string; fontFamily?: string; fontWeight?: string | number; offsetY?: number; color?: string; formatter?(val: string): string; };
      value?: { show?: boolean; fontSize?: string; fontFamily?: string; fontWeight?: string | number; offsetY?: number; color?: string; formatter?(val: number | string): string; };
      total?: { show?: boolean; showAlways?: boolean; label?: string; color?: string; fontFamily?: string; fontWeight?: string | number; fontSize?: string; formatter?(w: any): string; };
    };
  };
  radialBar?: {
    inverseOrder?: boolean;
    startAngle?: number;
    endAngle?: number;
    offsetX?: number;
    offsetY?: number;
    hollow?: {
      margin?: number;
      size?: string;
      background?: string;
      image?: string;
      imageWidth?: number;
      imageHeight?: number;
      imageOffsetX?: number;
      imageOffsetY?: number;
      imageClipped?: boolean;
      position?: 'front' | 'back';
      dropShadow?: { enabled?: boolean; top?: number; left?: number; blur?: number; opacity?: number; color?: string | string[]; };
    };
    track?: {
      show?: boolean;
      startAngle?: number;
      endAngle?: number;
      background?: string | string[];
      strokeWidth?: string;
      opacity?: number;
      margin?: number;
      dropShadow?: { enabled?: boolean; top?: number; left?: number; blur?: number; opacity?: number; color?: string | string[]; };
    };
    dataLabels?: {
      show?: boolean;
      name?: { show?: boolean; fontSize?: string; fontFamily?: string; fontWeight?: string | number; offsetY?: number; color?: string; };
      value?: { show?: boolean; fontSize?: string; fontFamily?: string; fontWeight?: string | number; offsetY?: number; color?: string; formatter?(val: number): string; };
      total?: { show?: boolean; label?: string; color?: string; fontFamily?: string; fontWeight?: string | number; fontSize?: string; formatter?(w: any): string; };
    };
  };
  radar?: {
    size?: number;
    offsetX?: number;
    offsetY?: number;
    polygons?: {
      strokeColors?: string | string[];
      strokeWidth?: string | number[];
      connectorColors?: string | string[];
      fill?: { colors?: string[]; };
    };
  };
  polarArea?: {
    rings?: { strokeColor?: string; strokeWidth?: number; };
    spokes?: { connectorColors?: string | string[]; strokeWidth?: number; };
  };
}</code></pre>

        <h4>مثال کاربردی (Bar):</h4>
        <pre><code>plotOptions: {
  bar: {
    horizontal: false,
    columnWidth: '70%',
    borderRadius: 4,
    borderRadiusApplication: 'end',
    distributed: false,
    dataLabels: {
      position: 'top',
      maxItems: 100,
      hideOverflowingLabels: true
    }
  }
}</code></pre>

        <h4>مثال کاربردی (Pie):</h4>
        <pre><code>plotOptions: {
  pie: {
    startAngle: 0,
    endAngle: 360,
    expandOnClick: true,
    expandOffset: 10,
    dataLabels: { offset: 0 }
  }
}</code></pre>

        <!-- responsive -->
        <h3>۳.۱۰. فیلد <code>responsive</code> - تنظیمات واکنش‌گرا</h3>
        <p>تنظیمات واکنش‌گرا برای اندازه‌های مختلف صفحه.</p>

        <h4>ساختار:</h4>
        <pre><code>responsive?: Array&lt;{
  breakpoint?: number;
  options?: ApexOptions;
}&gt;</code></pre>

        <h4>مثال کاربردی:</h4>
        <pre><code>responsive: [
  {
    breakpoint: 480,
    options: {
      chart: { width: '100%', height: 250 },
      legend: { position: 'bottom' }
    }
  },
  {
    breakpoint: 768,
    options: {
      chart: { width: '100%', height: 300 },
      legend: { position: 'bottom' }
    }
  }
]</code></pre>

        <!-- states -->
        <h3>۳.۱۱. فیلد <code>states</code> - حالت‌های مختلف</h3>
        <p>تنظیمات حالت‌های مختلف (hover, active).</p>

        <h4>ساختار:</h4>
        <pre><code>states?: {
  hover?: {
    filter?: {
      type?: 'none' | 'lighten' | 'darken';
      value?: number;
    };
  };
  active?: {
    allowMultipleDataPointsSelection?: boolean;
    filter?: {
      type?: 'none' | 'lighten' | 'darken';
      value?: number;
    };
  };
}</code></pre>

        <h4>مثال کاربردی:</h4>
        <pre><code>states: {
  hover: {
    filter: { type: 'darken', value: 0.15 }
  },
  active: {
    allowMultipleDataPointsSelection: false,
    filter: { type: 'darken', value: 0.35 }
  }
}</code></pre>

        <!-- stroke -->
        <h3>۳.۱۲. فیلد <code>stroke</code> - خطوط نمودار</h3>
        <p>تنظیمات خطوط نمودار.</p>

        <h4>ساختار:</h4>
        <pre><code>stroke?: {
  show?: boolean;
  curve?: 'smooth' | 'straight' | 'stepline' | 'linestep' | 'monotoneCubic' | 
          ('smooth' | 'straight' | 'stepline' | 'linestep' | 'monotoneCubic')[];
  lineCap?: 'butt' | 'square' | 'round';
  colors?: string[];
  width?: number | number[];
  dashArray?: number | number[];
}</code></pre>

        <h4>مثال کاربردی:</h4>
        <pre><code>stroke: {
  show: true,
  curve: 'smooth',
  lineCap: 'round',
  colors: ['#008FFB'],
  width: 3,
  dashArray: 0
}</code></pre>

        <!-- theme -->
        <h3>۳.۱۳. فیلد <code>theme</code> - تم</h3>
        <p>تنظیمات تم (روشن/تاریک).</p>

        <h4>ساختار:</h4>
        <pre><code>theme?: {
  mode?: 'light' | 'dark' | '';
  palette?: string;
  monochrome?: {
    enabled?: boolean;
    color?: string;
    shadeTo?: 'light' | 'dark';
    shadeIntensity?: number;
  };
}</code></pre>

        <h4>مثال کاربردی:</h4>
        <pre><code>theme: {
  mode: 'light',
  palette: 'palette1',
  monochrome: {
    enabled: false,
    color: '#008FFB',
    shadeTo: 'light',
    shadeIntensity: 0.65
  }
}</code></pre>

        <!-- title & subtitle -->
        <h3>۳.۱۴. فیلدهای <code>title</code> و <code>subtitle</code> - عنوان و زیرعنوان</h3>
        <p>تنظیمات عنوان و زیرعنوان نمودار.</p>

        <h4>ساختار:</h4>
        <pre><code>title?: {
  text?: string;
  align?: 'left' | 'center' | 'right';
  margin?: number;
  offsetX?: number;
  offsetY?: number;
  floating?: boolean;
  style?: {
    fontSize?: string;
    fontFamily?: string;
    fontWeight?: string | number;
    color?: string;
  };
}

subtitle?: {
  text?: string;
  align?: 'left' | 'center' | 'right';
  margin?: number;
  offsetX?: number;
  offsetY?: number;
  floating?: boolean;
  style?: {
    fontSize?: string;
    fontFamily?: string;
    fontWeight?: string | number;
    color?: string;
  };
}</code></pre>

        <h4>مثال کاربردی:</h4>
        <pre><code>title: {
  text: 'نمودار فروش ماهانه',
  align: 'center',
  margin: 10,
  style: {
    fontSize: '18px',
    fontFamily: 'Vazirmatn',
    fontWeight: 'bold',
    color: '#333'
  }
},
subtitle: {
  text: 'آمار فروش سال ۱۴۰۳',
  align: 'center',
  style: {
    fontSize: '14px',
    color: '#666'
  }
}</code></pre>

        <!-- tooltip -->
        <h3>۳.۱۵. فیلد <code>tooltip</code> - Tooltip</h3>
        <p>تنظیمات Tooltip (راهنمای شناور).</p>

        <h4>ساختار:</h4>
        <pre><code>tooltip?: {
  enabled?: boolean;
  enabledOnSeries?: number[];
  shared?: boolean;
  followCursor?: boolean;
  intersect?: boolean;
  inverseOrder?: boolean;
  arrow?: boolean;
  custom?: Array&lt;(opts: {
    series: number[][];
    seriesIndex: number;
    dataPointIndex: number;
    y1?: number;
    y2?: number;
    w: any;
  }) => string&gt;;
  fillSeriesColor?: boolean;
  theme?: 'light' | 'dark';
  style?: {
    fontSize?: string;
    fontFamily?: string;
  };
  onDatasetHover?: { highlightDataSeries?: boolean; };
  x?: {
    show?: boolean;
    format?: string;
    formatter?(value: string | number, opts?: any): string;
  };
  y?: Array&lt;{
    title?: { title?: string; formatter?(seriesName: string, opts?: any): string; };
    formatter?(value: number, opts?: any): string;
  }&gt;;
  marker?: { show?: boolean; fillColors?: string[]; };
  items?: { display?: string; };
  fixed?: {
    enabled?: boolean;
    position?: string;
    offsetX?: number;
    offsetY?: number;
  };
}</code></pre>

        <h4>مثال کاربردی:</h4>
        <pre><code>tooltip: {
  enabled: true,
  shared: true,
  followCursor: false,
  intersect: false,
  theme: 'light',
  style: { fontSize: '12px', fontFamily: 'Vazirmatn' },
  x: { show: true, format: 'dd MMM' },
  y: [{
    title: { title: 'فروش: ' },
    formatter: (val) => val.toLocaleString('fa-IR') + ' تومان'
  }],
  marker: { show: true }
}</code></pre>

        <!-- xaxis -->
        <h3>۳.۱۶. فیلد <code>xaxis</code> - محور X</h3>
        <p>تنظیمات محور X.</p>

        <h4>ساختار:</h4>
        <pre><code>xaxis?: {
  type?: 'category' | 'datetime' | 'numeric';
  categories?: Array&lt;string | number&gt; | Array&lt;Array&lt;string | number&gt;&gt;;
  overwriteCategories?: number[] | string[];
  offsetX?: number;
  offsetY?: number;
  sorted?: boolean;
  labels?: {
    show?: boolean;
    rotate?: number;
    rotateAlways?: boolean;
    hideOverlappingLabels?: boolean;
    showDuplicates?: boolean;
    trim?: boolean;
    minHeight?: number;
    maxHeight?: number;
    style?: {
      colors?: string | string[];
      fontSize?: string;
      fontFamily?: string;
      fontWeight?: string | number;
      cssClass?: string;
    };
    offsetX?: number;
    offsetY?: number;
    format?: string;
    formatter?(value: string | number, timestamp?: number, opts?: any): string | string[];
    datetimeUTC?: boolean;
    datetimeFormatter?: {
      year?: string;
      month?: string;
      day?: string;
      hour?: string;
      minute?: string;
      second?: string;
    };
  };
  group?: {
    groups?: Array&lt;{ title: string; cols: number }&gt;;
    style?: {
      colors?: string | string[];
      fontSize?: string;
      fontFamily?: string;
      fontWeight?: string | number;
      cssClass?: string;
    };
  };
  axisBorder?: {
    show?: boolean;
    color?: string;
    width?: string | number;
    height?: number;
    offsetX?: number;
    offsetY?: number;
  };
  axisTicks?: {
    show?: boolean;
    borderType?: 'solid' | 'dotted';
    color?: string;
    width?: number;
    offsetX?: number;
    offsetY?: number;
  };
  tickPlacement?: string;
  tickAmount?: number | 'dataPoints';
  stepSize?: number;
  min?: number;
  max?: number;
  range?: number;
  floating?: boolean;
  position?: string;
  title?: {
    text?: string;
    offsetX?: number;
    offsetY?: number;
    style?: {
      color?: string;
      fontFamily?: string;
      fontWeight?: string | number;
      fontSize?: string;
      cssClass?: string;
    };
  };
  crosshairs?: {
    show?: boolean;
    width?: number | string;
    position?: string;
    opacity?: number;
    stroke?: { color?: string; width?: number; dashArray?: number; };
    fill?: {
      type?: string;
      color?: string;
      gradient?: {
        colorFrom?: string;
        colorTo?: string;
        stops?: number[];
        opacityFrom?: number;
        opacityTo?: number;
      };
    };
    dropShadow?: { enabled?: boolean; top?: number; left?: number; blur?: number; opacity?: number; color?: string | string[]; };
  };
  tooltip?: {
    enabled?: boolean;
    offsetY?: number;
    formatter?(value: string | number, opts?: any): string;
    style?: { fontSize?: string; fontFamily?: string; };
  };
}</code></pre>

        <h4>مثال کاربردی:</h4>
        <pre><code>xaxis: {
  type: 'category',
  categories: ['فروردین', 'اردیبهشت', 'خرداد', 'تیر', 'مرداد', 'شهریور', 'مهر'],
  labels: {
    show: true,
    rotate: -45,
    style: {
      fontSize: '12px',
      fontFamily: 'Vazirmatn',
      colors: ['#333']
    }
  },
  title: {
    text: 'ماه',
    style: { fontSize: '14px', fontWeight: 'bold' }
  },
  crosshairs: {
    show: true,
    stroke: { color: '#000', width: 1, dashArray: 0 }
  }
}</code></pre>

        <!-- yaxis -->
        <h3>۳.۱۷. فیلد <code>yaxis</code> - محور Y</h3>
        <p>تنظیمات محور Y.</p>

        <h4>ساختار:</h4>
        <pre><code>yaxis?: {
  show?: boolean;
  showAlways?: boolean;
  showForNullSeries?: boolean;
  seriesName?: string | string[];
  opposite?: boolean;
  reversed?: boolean;
  logarithmic?: boolean;
  logBase?: number;
  tickAmount?: number;
  stepSize?: number;
  forceNiceScale?: boolean;
  min?: number | ((min: number) => number);
  max?: number | ((max: number) => number);
  floating?: boolean;
  decimalsInFloat?: number;
  labels?: {
    show?: boolean;
    showDuplicates?: boolean;
    minWidth?: number;
    maxWidth?: number;
    style?: {
      colors?: string | string[];
      fontSize?: string;
      fontWeight?: string | number;
      fontFamily?: string;
      cssClass?: string;
    };
    offsetX?: number;
    offsetY?: number;
    rotate?: number;
    align?: 'left' | 'center' | 'right';
    padding?: number;
    formatter?(value: number, opts?: any): string | string[];
  };
  axisBorder?: {
    show?: boolean;
    color?: string;
    width?: number;
    offsetX?: number;
    offsetY?: number;
  };
  axisTicks?: {
    show?: boolean;
    borderType?: 'solid' | 'dotted';
    color?: string;
    width?: number;
    offsetX?: number;
    offsetY?: number;
  };
  title?: {
    text?: string;
    rotate?: number;
    offsetX?: number;
    offsetY?: number;
    style?: {
      color?: string;
      fontFamily?: string;
      fontWeight?: string | number;
      fontSize?: string;
      cssClass?: string;
    };
  };
  crosshairs?: {
    show?: boolean;
    position?: string;
    stroke?: { color?: string; width?: number; dashArray?: number; };
  };
  tooltip?: {
    enabled?: boolean;
    offsetX?: number;
  };
}</code></pre>

        <h4>مثال کاربردی:</h4>
        <pre><code>yaxis: {
  show: true,
  title: {
    text: 'مبلغ (تومان)',
    style: { fontSize: '14px', fontWeight: 'bold' }
  },
  labels: {
    show: true,
    formatter: (val) => val.toLocaleString('fa-IR'),
    style: { fontSize: '12px', fontFamily: 'Vazirmatn' }
  },
  min: 0,
  max: 100,
  tickAmount: 5
}</code></pre>

        <!-- noData -->
        <h3>۳.۱۸. فیلد <code>noData</code> - پیام عدم وجود داده</h3>
        <p>تنظیمات پیام نمایش داده شده در صورت عدم وجود داده.</p>

        <h4>ساختار:</h4>
        <pre><code>noData?: {
  text?: string;
  align?: 'left' | 'center' | 'right';
  verticalAlign?: 'top' | 'middle' | 'bottom';
  offsetX?: number;
  offsetY?: number;
  style?: {
    color?: string;
    fontSize?: string;
    fontFamily?: string;
  };
}</code></pre>

        <h4>مثال کاربردی:</h4>
        <pre><code>noData: {
  text: 'داده‌ای برای نمایش وجود ندارد',
  align: 'center',
  verticalAlign: 'middle',
  style: {
    color: '#999',
    fontSize: '16px',
    fontFamily: 'Vazirmatn'
  }
}</code></pre>

        <!-- Summary -->
        <h2>۴. خلاصه و جمع‌بندی</h2>
        <p>
            مدل <code>ApexOptions</code> با ۲۵ فیلد اصلی، کنترل کاملی بر تمام جنبه‌های نمودار ارائه می‌دهد. با استفاده از این فیلدها می‌توانید نمودارهای کاملاً سفارشی و حرفه‌ای ایجاد کنید.
        </p>

        <div class="info-box">
            <strong>💡 نکته مهم:</strong> برای استفاده بهینه، همیشه از مستندات رسمی ApexCharts (<a href="https://apexcharts.com/docs/options" target="_blank">apexcharts.com/docs/options</a>) استفاده کنید و مقادیر پیش‌فرض را بررسی کنید.
        </div>

        <div class="warning-box">
            <strong>⚠️ هشدار:</strong> برخی فیلدها فقط برای انواع خاصی از نمودار کاربرد دارند. قبل از استفاده، مطمئن شوید که فیلد مورد نظر با نوع نمودار شما سازگار است.
        </div>

        <!-- Footer -->
        <div style="margin-top: 60px; border-top: 1px solid #ddd; padding-top: 20px; text-align: center; color: #777; font-size: 13px;">
            <p>© کلیه حقوق این مستند متعلق به <strong>شرکت فن آوران ساحر علم</strong> می‌باشد.</p>
            <p>طراحی و توسعه: <strong>هادی خزاعی اصل</strong> | وب‌سایت: <a href="https://saherelm.ir" style="color: #3498db; text-decoration: none;">https://saherelm.ir</a></p>
            <p>📅 تاریخ تهیه: دوشنبه ۱۱ شهریور ۱۴۰۴</p>
        </div>
    </div>

</body>
</html>

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Resources\غالب پروژه ها.html

<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>پروژه نمونه</title>
  <style>
    @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap');
    /* ============ Theme Variables ============ */
    :root {
      --primary: #1a365d;
      --primary-light: #2c5282;
      --primary-dark: #0f2341;
      --accent: #c9a961;
      --accent-dark: #a8893f;
      --accent-light: #e0c789;
      --bg: #f5f7fa;
      --board-bg: #e2e8f0;
      --card-bg: #ffffff;
      --column-bg: #edf2f7;
      --panel-bg: #ffffff;
      --text: #1a202c;
      --text-muted: #4a5568;
      --text-light: #718096;
      --border: #cbd5e0;
      --border-light: #e2e8f0;
      --success: #38a169;
      --danger: #e53e3e;
      --warning: #dd6b20;
      --info: #3182ce;
      --shadow: 0 4px 6px rgba(0,0,0,0.08), 0 10px 20px rgba(26,54,93,0.1);
      --shadow-sm: 0 2px 4px rgba(0,0,0,0.06);
      --shadow-lg: 0 10px 30px rgba(0,0,0,0.15);
      --radius: 10px;
      --radius-sm: 6px;
      --sidebar-width: 280px;
      --header-height: 64px;
      --tabs-height: 48px;
      --footer-height: 36px;
    }
    [data-theme="dark"] {
      --primary: #2c5282;
      --primary-light: #3b6fb5;
      --primary-dark: #1a365d;
      --accent: #e0c789;
      --accent-dark: #c9a961;
      --accent-light: #f0d9a0;
      --bg: #1a202c;
      --board-bg: #2d3748;
      --card-bg: #2d3748;
      --column-bg: #374151;
      --panel-bg: #1f2937;
      --text: #e2e8f0;
      --text-muted: #cbd5e0;
      --text-light: #a0aec0;
      --border: #4a5568;
      --border-light: #374151;
      --shadow: 0 4px 6px rgba(0,0,0,0.3), 0 10px 20px rgba(0,0,0,0.4);
      --shadow-sm: 0 2px 4px rgba(0,0,0,0.2);
      --shadow-lg: 0 10px 30px rgba(0,0,0,0.5);
    }
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body {
      font-family: 'Vazirmatn', Tahoma, sans-serif;
      background: var(--bg);
      color: var(--text);
      height: 100vh;
      overflow: hidden;
      font-size: 14px;
      display: flex;
      flex-direction: column;
      transition: background 0.3s, color 0.3s;
    }
    /* ============ Header ============ */
    .app-header {
      background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);
      color: white;
      padding: 0 20px;
      height: var(--header-height);
      display: flex;
      align-items: center;
      justify-content: space-between;
      border-bottom: 3px solid var(--accent);
      box-shadow: var(--shadow);
      z-index: 100;
      flex-shrink: 0;
      position: relative;
      overflow: hidden;
    }
    .app-header::before {
      content: '';
      position: absolute;
      top: -50%;
      left: -10%;
      width: 300px;
      height: 300px;
      background: radial-gradient(circle, rgba(201,169,97,0.15) 0%, transparent 70%);
      border-radius: 50%;
      pointer-events: none;
    }
    .app-brand {
      display: flex;
      align-items: center;
      gap: 14px;
      position: relative;
      z-index: 1;
    }
    .app-logo {
      width: 44px; height: 44px;
      background: var(--accent);
      border-radius: 10px;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 22px;
      font-weight: 800;
      color: var(--primary-dark);
      box-shadow: 0 4px 12px rgba(0,0,0,0.25);
      flex-shrink: 0;
    }
    .app-brand-text {
      display: flex;
      flex-direction: column;
      gap: 2px;
    }
    .app-title {
      font-size: 16px;
      font-weight: 800;
      letter-spacing: -0.3px;
      color: white;
    }
    .app-subtitle {
      font-size: 11px;
      opacity: 0.85;
      color: rgba(255,255,255,0.9);
    }
    /* ============ Buttons ============ */
    .btn {
      background: linear-gradient(135deg, var(--primary-light), var(--primary));
      color: white;
      border: none;
      padding: 8px 14px;
      border-radius: var(--radius-sm);
      cursor: pointer;
      font-family: inherit;
      font-size: 13px;
      font-weight: 600;
      display: inline-flex;
      align-items: center;
      gap: 6px;
      transition: all 0.2s;
      box-shadow: 0 2px 4px rgba(0,0,0,0.15);
      white-space: nowrap;
    }
    .btn:hover { transform: translateY(-1px); box-shadow: 0 4px 8px rgba(0,0,0,0.2); filter: brightness(1.1); }
    .btn:active { transform: translateY(0); }
    .btn-accent {
      background: linear-gradient(135deg, var(--accent), var(--accent-dark));
      color: var(--primary-dark);
      font-weight: 700;
    }
    .btn-ghost {
      background: rgba(26,54,93,0.08);
      border: 1px solid var(--border);
      color: var(--primary);
      font-weight: 600;
    }
    .btn-ghost:hover {
      background: rgba(26,54,93,0.15);
      border-color: var(--primary-light);
    }
    [data-theme="dark"] .btn-ghost {
      background: rgba(255,255,255,0.1);
      border: 1px solid rgba(255,255,255,0.25);
      color: white;
    }
    [data-theme="dark"] .btn-ghost:hover {
      background: rgba(255,255,255,0.2);
    }
    /* ============ Main Layout ============ */
    .app-main {
      flex: 1;
      display: flex;
      overflow: hidden;
      position: relative;
      flex-direction: row;
    }
    /* ============ Sidebar ============ */
    .sidebar {
      order: -1;
      width: var(--sidebar-width);
      background: var(--panel-bg);
      border-right: 1px solid var(--border);
      display: flex;
      flex-direction: column;
      transition: width 0.3s ease;
      box-shadow: var(--shadow);
      overflow: hidden;
      flex-shrink: 0;
      z-index: 50;
    }
    .sidebar.collapsed {
      width: 0;
      overflow: hidden;
    }
    .sidebar-header {
      background: linear-gradient(135deg, var(--primary), var(--primary-light));
      color: white;
      padding: 12px;
      display: flex;
      align-items: center;
      justify-content: space-between;
      border-bottom: 2px solid var(--accent);
      min-height: 48px;
    }
    .sidebar-title {
      font-size: 13px;
      font-weight: 700;
      display: flex;
      align-items: center;
      gap: 8px;
      white-space: nowrap;
      overflow: hidden;
    }
    .sidebar.collapsed .sidebar-title { display: none; }
    .sidebar-body {
      flex: 1;
      overflow-y: auto;
      padding: 8px;
    }
    .sidebar.collapsed .sidebar-body { display: none; }
    .sidebar-group {
      margin-bottom: 6px;
      border: 1px solid var(--border-light);
      border-radius: var(--radius-sm);
      overflow: hidden;
      background: var(--card-bg);
    }
    .sidebar-group-header {
      background: linear-gradient(135deg, var(--primary), var(--primary-light));
      color: white;
      padding: 10px 12px;
      cursor: pointer;
      display: flex;
      align-items: center;
      justify-content: space-between;
      font-size: 13px;
      font-weight: 700;
      user-select: none;
      transition: all 0.2s;
      position: relative;
    }
    .sidebar-group-header:hover { filter: brightness(1.1); }
    .sidebar-group-header .group-icon {
      display: flex;
      align-items: center;
      gap: 8px;
      flex: 1;
      overflow: hidden;
    }
    .sidebar-group-header .group-arrow {
      transition: transform 0.3s;
      font-size: 12px;
      flex-shrink: 0;
    }
    .sidebar-group.collapsed .group-arrow { transform: rotate(90deg); }
    .sidebar-group.collapsed .sidebar-group-body { display: none; }
    .sidebar-group-body {
      padding: 6px;
      display: flex;
      flex-direction: column;
      gap: 4px;
    }
    .sidebar-btn {
      background: var(--card-bg);
      border: 1px solid var(--border-light);
      color: var(--text);
      padding: 8px 10px;
      border-radius: var(--radius-sm);
      cursor: pointer;
      font-family: inherit;
      font-size: 12px;
      font-weight: 500;
      display: flex;
      align-items: center;
      gap: 8px;
      transition: all 0.2s;
      text-align: right;
      width: 100%;
      position: relative;
    }
    .sidebar-btn:hover {
      background: linear-gradient(135deg, var(--primary), var(--primary-light));
      color: white;
      border-color: var(--primary);
      transform: translateX(-2px);
    }
    .sidebar-btn .btn-icon-emoji {
      font-size: 15px;
      width: 20px;
      text-align: center;
      flex-shrink: 0;
    }
    .sidebar-btn .btn-title {
      flex: 1;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
    .sidebar-btn .shortcut-badge {
      font-size: 9px;
      font-weight: 700;
      padding: 2px 6px;
      background: rgba(0,0,0,0.08);
      color: var(--text-muted);
      border-radius: 4px;
      font-family: 'Vazirmatn', monospace;
      flex-shrink: 0;
      letter-spacing: 0.5px;
    }
    .sidebar-btn:hover .shortcut-badge {
      background: rgba(255,255,255,0.2);
      color: white;
    }
    /* ============ Tooltip ============ */
    [data-tooltip] {
      position: relative;
    }
    [data-tooltip]::after {
      content: attr(data-tooltip);
      position: absolute;
      right: calc(100% + 10px);
      top: 50%;
      transform: translateY(-50%);
      background: var(--primary-dark);
      color: white;
      padding: 8px 12px;
      border-radius: 6px;
      font-size: 11px;
      font-weight: 500;
      white-space: nowrap;
      opacity: 0;
      pointer-events: none;
      transition: opacity 0.2s;
      z-index: 1000;
      box-shadow: 0 4px 12px rgba(0,0,0,0.3);
      max-width: 250px;
      white-space: normal;
      line-height: 1.5;
    }
    [data-tooltip]::before {
      content: '';
      position: absolute;
      right: calc(100% + 5px);
      top: 50%;
      transform: translateY(-50%);
      border: 5px solid transparent;
      border-left-color: var(--primary-dark);
      opacity: 0;
      pointer-events: none;
      transition: opacity 0.2s;
      z-index: 1000;
    }
    [data-tooltip]:hover::after,
    [data-tooltip]:hover::before {
      opacity: 1;
    }
    [data-theme="dark"] [data-tooltip]::after {
      background: var(--accent-dark);
      color: var(--primary-dark);
    }
    [data-theme="dark"] [data-tooltip]::before {
      border-left-color: var(--accent-dark);
    }
    /* ============ Tabs Bar ============ */
    .tabs-bar {
      background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary) 100%);
      border-bottom: 2px solid var(--accent);
      display: flex;
      align-items: center;
      padding: 0 10px;
      min-height: var(--tabs-height);
      box-shadow: var(--shadow-sm);
      flex-shrink: 0;
      gap: 6px;
      position: relative;
    }
    [data-theme="dark"] .tabs-bar {
      background: linear-gradient(135deg, #0f1724 0%, #1a2a44 100%);
    }
    .tab-control-btn {
      background: rgba(255,255,255,0.1);
      border: 1px solid rgba(255,255,255,0.15);
      color: white;
      width: 32px; height: 32px;
      border-radius: 6px;
      cursor: pointer;
      font-size: 14px;
      display: flex;
      align-items: center;
      justify-content: center;
      transition: all 0.2s;
      padding: 0;
    }
    .tab-control-btn:hover:not(:disabled) {
      background: var(--accent);
      color: var(--primary-dark);
      border-color: var(--accent);
      transform: translateY(-1px);
    }
    /* ============ Board Area ============ */
    .board-area {
      flex: 1;
      overflow: auto;
      padding: 20px;
      background: var(--board-bg);
      transition: background 0.3s;
    }
    .board-empty {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      height: 100%;
      color: var(--text-muted);
      text-align: center;
    }
    .board-empty-icon { font-size: 80px; opacity: 0.3; margin-bottom: 20px; }
    .board-empty h2 { font-size: 20px; color: var(--primary); margin-bottom: 10px; }
    [data-theme="dark"] .board-empty h2 { color: var(--accent); }
    .board-empty p { margin-bottom: 20px; }
    /* ============ Modals ============ */
    .modal-overlay {
      position: fixed;
      inset: 0;
      background: rgba(26,54,93,0.6);
      backdrop-filter: blur(3px);
      display: none;
      align-items: center;
      justify-content: center;
      z-index: 1000;
      padding: 20px;
      animation: fadeIn 0.2s;
    }
    .modal-overlay.active { display: flex; }
    @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
    .modal {
      background: var(--panel-bg);
      color: var(--text);
      border-radius: 12px;
      width: 100%;
      max-width: 600px;
      max-height: 90vh;
      overflow: hidden;
      display: flex;
      flex-direction: column;
      box-shadow: var(--shadow-lg);
      animation: slideUp 0.25s;
    }
    .modal-lg { max-width: 800px; }
    @keyframes slideUp {
      from { opacity: 0; transform: translateY(20px); }
      to { opacity: 1; transform: translateY(0); }
    }
    .modal-header {
      background: linear-gradient(135deg, var(--primary), var(--primary-light));
      color: white;
      padding: 16px 20px;
      display: flex;
      align-items: center;
      justify-content: space-between;
      border-bottom: 3px solid var(--accent);
    }
    .modal-title {
      font-size: 16px;
      font-weight: 700;
      display: flex;
      align-items: center;
      gap: 10px;
    }
    .modal-close {
      background: rgba(255,255,255,0.15);
      border: none;
      color: white;
      width: 30px; height: 30px;
      border-radius: 50%;
      cursor: pointer;
      font-size: 16px;
      transition: all 0.2s;
    }
    .modal-close:hover { background: var(--danger); }
    .modal-body {
      padding: 20px;
      overflow-y: auto;
      flex: 1;
    }
    .modal-footer {
      padding: 14px 20px;
      border-top: 1px solid var(--border);
      display: flex;
      justify-content: flex-start;
      gap: 8px;
      background: var(--border-light);
    }
    /* ============ Forms ============ */
    .form-group { margin-bottom: 16px; }
    .form-label {
      display: block;
      font-size: 13px;
      font-weight: 600;
      color: var(--primary);
      margin-bottom: 6px;
    }
    [data-theme="dark"] .form-label { color: var(--accent-light); }
    .form-label .required { color: var(--danger); }
    .form-input, .form-textarea, .form-select {
      width: 100%;
      padding: 9px 12px;
      border: 1px solid var(--border);
      border-radius: var(--radius-sm);
      font-family: inherit;
      font-size: 13px;
      background: var(--card-bg);
      color: var(--text);
      transition: all 0.2s;
    }
    .form-input:focus, .form-textarea:focus, .form-select:focus {
      outline: none;
      border-color: var(--accent);
      box-shadow: 0 0 0 3px rgba(201,169,97,0.15);
    }
    .form-textarea { resize: vertical; min-height: 70px; line-height: 1.6; }
    .form-row {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 12px;
    }
    .form-hint {
      font-size: 11px;
      color: var(--text-muted);
      margin-top: 4px;
    }
    /* ============ Webhook Section ============ */
    .webhook-section {
      background: var(--border-light);
      border: 1px solid var(--border);
      border-radius: var(--radius-sm);
      padding: 14px;
      margin-top: 12px;
    }
    .webhook-section-header {
      display: flex;
      align-items: center;
      justify-content: space-between;
      margin-bottom: 10px;
    }
    .webhook-section-title {
      font-size: 13px;
      font-weight: 700;
      color: var(--primary);
      display: flex;
      align-items: center;
      gap: 6px;
    }
    [data-theme="dark"] .webhook-section-title { color: var(--accent-light); }
    .webhook-toggle {
      display: flex;
      align-items: center;
      gap: 6px;
      font-size: 11px;
      color: var(--text-muted);
    }
    .switch {
      position: relative;
      display: inline-block;
      width: 36px;
      height: 20px;
    }
    .switch input { opacity: 0; width: 0; height: 0; }
    .slider {
      position: absolute;
      cursor: pointer;
      inset: 0;
      background: var(--border);
      transition: 0.3s;
      border-radius: 20px;
    }
    .slider::before {
      position: absolute;
      content: "";
      height: 14px;
      width: 14px;
      right: 3px;
      bottom: 3px;
      background: white;
      transition: 0.3s;
      border-radius: 50%;
    }
    input:checked + .slider { background: var(--success); }
    input:checked + .slider::before { transform: translateX(-16px); }
    .webhook-status {
      display: flex;
      align-items: center;
      gap: 8px;
      margin-top: 10px;
      padding: 8px 10px;
      background: var(--card-bg);
      border-radius: 4px;
      font-size: 11px;
      color: var(--text-muted);
    }
    .webhook-status-dot {
      width: 8px;
      height: 8px;
      border-radius: 50%;
      background: var(--text-muted);
      flex-shrink: 0;
    }
    .webhook-status-dot.success { background: var(--success); }
    .webhook-status-dot.error { background: var(--danger); }
    .webhook-status-dot.pending { background: var(--warning); animation: pulse 1.5s infinite; }
    @keyframes pulse {
      0%, 100% { opacity: 1; }
      50% { opacity: 0.4; }
    }
    /* ============ Toast ============ */
    .toast-container {
      position: fixed;
      bottom: 20px;
      left: 20px;
      z-index: 2000;
      display: flex;
      flex-direction: column;
      gap: 8px;
    }
    .toast {
      background: var(--primary);
      color: white;
      padding: 12px 18px;
      border-radius: 8px;
      box-shadow: var(--shadow-lg);
      font-size: 13px;
      display: flex;
      align-items: center;
      gap: 10px;
      animation: slideInLeft 0.3s;
      min-width: 250px;
    }
    .toast.success { background: var(--success); }
    .toast.error { background: var(--danger); }
    .toast.warning { background: var(--warning); }
    @keyframes slideInLeft {
      from { opacity: 0; transform: translateX(-30px); }
      to { opacity: 1; transform: translateX(0); }
    }
    /* ============ Footer ============ */
    .app-footer {
      background: linear-gradient(135deg, var(--primary), var(--primary-light));
      color: white;
      padding: 8px 20px;
      font-size: 11px;
      display: flex;
      justify-content: space-between;
      align-items: center;
      border-top: 2px solid var(--accent);
      flex-shrink: 0;
    }
    .footer-brand { color: var(--accent); font-weight: 700; }
    /* ============ Readonly Fields Style ============ */
    .form-input[readonly],
    .form-textarea[readonly] {
      background: var(--border-light);
      color: var(--text-muted);
      cursor: not-allowed;
      opacity: 0.85;
      border-style: dashed;
    }
    .form-input[readonly]:focus,
    .form-textarea[readonly]:focus {
      border-color: var(--border);
      box-shadow: none;
    }
    .readonly-badge {
      display: inline-flex;
      align-items: center;
      gap: 4px;
      font-size: 10px;
      font-weight: 600;
      color: var(--text-muted);
      background: var(--border-light);
      border: 1px solid var(--border);
      padding: 2px 8px;
      border-radius: 10px;
      margin-right: 6px;
    }
    .readonly-badge::before {
      content: '🔒';
      font-size: 10px;
    }
    .readonly-info {
      background: linear-gradient(to left, #ebf8ff, #e6f6ff);
      border: 1px solid #90cdf4;
      border-right: 4px solid var(--info);
      padding: 10px 14px;
      border-radius: 6px;
      font-size: 11px;
      color: var(--text-muted);
      margin-bottom: 14px;
      display: flex;
      align-items: center;
      gap: 8px;
    }
    [data-theme="dark"] .readonly-info {
      background: rgba(49, 130, 206, 0.12);
      border-color: rgba(49, 130, 206, 0.3);
      border-right-color: var(--info);
    }
    /* ============ Scrollbar ============ */
    ::-webkit-scrollbar { width: 8px; height: 8px; }
    ::-webkit-scrollbar-track { background: transparent; }
    ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
    ::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
  </style>
</head>
<body>
  <!-- Header -->
  <header class="app-header">
    <div class="app-brand">
      <div class="app-logo">📊</div>
      <div class="app-brand-text">
        <div class="app-title">پروژه نمونه</div>
        <div class="app-subtitle">توضیحات پروژه</div>
      </div>
    </div>
  </header>

  <!-- Toolbar -->
  <div class="tabs-bar">
    <button class="tab-control-btn" id="sidebarToggle" title="باز/بسته کردن سایدبار">
      <span id="sidebarIcon">☰</span>
    </button>
    <button class="tab-control-btn" id="themeToggle" title="تغییر تم">
      <span id="themeIcon">🌙</span>
    </button>
  </div>

  <!-- Main Layout -->
  <div class="app-main">
    <!-- Sidebar -->
    <aside class="sidebar" id="sidebar">
      <div class="sidebar-header">
        <div class="sidebar-title">🛠️ ابزارها</div>
      </div>
      <div class="sidebar-body" id="sidebarBody"></div>
    </aside>

    <!-- Board Area -->
    <main class="board-area">
      <div class="board-empty">
        <div class="board-empty-icon">📋</div>
        <h2>ناحیه اصلی</h2>
        <p>محتوای اصلی برنامه در این ناحیه نمایش داده می‌شود</p>
      </div>
    </main>
  </div>

  <!-- Footer -->
  <footer class="app-footer">
    <div>
      <span class="footer-brand">طراح و توسعه‌دهنده:</span> هادی خزاعی اصل
      <span style="margin: 0 10px;">|</span>
      <span class="footer-brand">شرکت:</span> رایان پرداز کاوش
    </div>
    <div>
      <span class="footer-brand">نسخه:</span> 1.0.0
    </div>
  </footer>

  <!-- Settings Modal -->
  <div class="modal-overlay" id="settingsModal">
    <div class="modal modal-lg">
      <div class="modal-header">
        <div class="modal-title">
          <span>⚙️</span>
          <span>تنظیمات پروژه</span>
        </div>
        <button class="modal-close" data-close-modal>✕</button>
      </div>
      <div class="modal-body">
        <div class="readonly-info">
          <span>ℹ️</span>
          <span>اطلاعات پایه پروژه (عنوان، نسخه، توضیحات، توسعه‌دهنده و شرکت) فقط قابل مشاهده هستند و از طریق فایل پیکربندی پروژه قابل تغییر می‌باشند.</span>
        </div>

        <div class="form-row">
          <div class="form-group">
            <label class="form-label">
              عنوان پروژه
              <span class="readonly-badge">فقط خواندنی</span>
            </label>
            <input type="text" class="form-input" id="settingProjectTitle" readonly>
          </div>
          <div class="form-group">
            <label class="form-label">
              نسخه
              <span class="readonly-badge">فقط خواندنی</span>
            </label>
            <input type="text" class="form-input" id="settingVersion" readonly>
          </div>
        </div>
        <div class="form-group">
          <label class="form-label">
            توضیحات
            <span class="readonly-badge">فقط خواندنی</span>
          </label>
          <textarea class="form-textarea" id="settingDescription" readonly></textarea>
        </div>
        <div class="form-row">
          <div class="form-group">
            <label class="form-label">
              توسعه‌دهنده
              <span class="readonly-badge">فقط خواندنی</span>
            </label>
            <input type="text" class="form-input" id="settingDeveloper" readonly>
          </div>
          <div class="form-group">
            <label class="form-label">
              شرکت
              <span class="readonly-badge">فقط خواندنی</span>
            </label>
            <input type="text" class="form-input" id="settingCompany" readonly>
          </div>
        </div>

        <div class="webhook-section">
          <div class="webhook-section-header">
            <div class="webhook-section-title">
              <span>🔗</span>
              <span>تنظیمات وب‌هوک (Webhook)</span>
            </div>
            <div class="webhook-toggle">
              <span>فعال‌سازی</span>
              <label class="switch">
                <input type="checkbox" id="settingWebhookEnabled">
                <span class="slider"></span>
              </label>
            </div>
          </div>

          <div class="form-group">
            <label class="form-label">آدرس URL وب‌هوک <span class="required">*</span></label>
            <input type="url" class="form-input" id="settingWebhookUrl" dir="ltr" placeholder="https://example.com/webhook">
            <div class="form-hint">آدرسی که رویدادها به آن ارسال می‌شوند (باید با http یا https شروع شود)</div>
          </div>

          <div class="form-group">
            <label class="form-label">شناسه کلاینت (Client ID) <span class="required">*</span></label>
            <input type="text" class="form-input" id="settingWebhookClientId" dir="ltr" placeholder="client-xxxx-xxxx">
            <div class="form-hint">این شناسه در هدر درخواست با کلید X-Client-Id ارسال می‌شود</div>
          </div>

          <div class="webhook-status" id="webhookStatus">
            <span class="webhook-status-dot" id="webhookStatusDot"></span>
            <span id="webhookStatusText">وضعیت: غیرفعال</span>
          </div>
        </div>
      </div>
      <div class="modal-footer">
        <button class="btn btn-accent" id="saveSettingsBtn">💾 ذخیره</button>
        <button class="btn btn-ghost" data-close-modal>بستن</button>
      </div>
    </div>
  </div>

  <!-- Toast Container -->
  <div class="toast-container" id="toastContainer"></div>

  <script>
    // ============ Project Data Structure (New Schema) ============
    const projectData = {
      projectId: "550e8400-e29b-41d4-a716-446655440000",
      projectName: "sample_project",
      projectTitle: "پروژه نمونه",
      description: "توضیحات پروژه",
      version: "1.0.0",
      developer: "هادی خزاعی اصل",
      company: "رایان پرداز کاوش",
      webhook: {
        url: "",
        clientId: "",
        enabled: false
      },
      toolGroups: [
        {
          id: 'project-management',
          title: 'پروژه',
          icon: '📁',
          description: 'مدیریت چرخه حیات پروژه',
          tools: [
            {
              id: 'project-settings',
              title: 'تنظیمات پروژه',
              icon: '⚙️',
              action: 'openSettings',
              shortcut: 'Ctrl+,',
              description: 'مشاهده اطلاعات پروژه و تنظیمات وب‌هوک',
              targetEntity: 'settings'
            },
            {
              id: 'create-project',
              title: 'ایجاد پروژه جدید',
              icon: '➕',
              action: 'createProject',
              shortcut: 'Ctrl+N',
              description: 'ایجاد یک پروژه جدید با متادیتای اولیه',
              targetEntity: 'projects'
            }
          ]
        },
        {
          id: 'data-analytics',
          title: 'ابزارهای تحلیل داده',
          icon: '📊',
          description: 'ابزارهای تحلیل و پردازش داده‌ها',
          tools: [
            {
              id: 'chart-generator',
              title: 'نمودار ساز پویا',
              icon: '📈',
              action: 'generateChart',
              shortcut: 'Ctrl+G',
              description: 'ابزاری برای تبدیل جداول داده به نمودارهای میله‌ای و دایره‌ای به صورت زنده',
              targetEntity: 'charts'
            },
            {
              id: 'advanced-filter',
              title: 'فیلتر پیشرفته',
              icon: '🔍',
              action: 'applyFilter',
              shortcut: 'Ctrl+F',
              description: 'امکان اعمال فیلترهای چندلایه و شرطی بر روی مجموعه داده‌ها',
              targetEntity: 'datasets'
            }
          ]
        },
        {
          id: 'export-tools',
          title: 'ابزارهای خروجی و گزارش',
          icon: '📤',
          description: 'ابزارهای تولید گزارش و خروجی',
          tools: [
            {
              id: 'pdf-export',
              title: 'خروجی PDF',
              icon: '📄',
              action: 'exportPDF',
              shortcut: 'Ctrl+E',
              description: 'تولید فایل PDF استاندارد از گزارش‌های فعلی با قابلیت شخصی‌سازی هدر و فوتر',
              targetEntity: 'reports'
            }
          ]
        }
      ]
    };

    // ============ Action Registry ============
    const ActionRegistry = {
      openSettings(tool, group) {
        openSettingsModal();
      },
      createProject(tool, group) {
        showToast(`🚀 اکشن "${tool.action}" اجرا شد - ${tool.title}`, 'info', 3000);
      },
      generateChart(tool, group) {
        showToast(`📈 اکشن "${tool.action}" اجرا شد - ${tool.title}`, 'info', 3000);
      },
      applyFilter(tool, group) {
        showToast(`🔍 اکشن "${tool.action}" اجرا شد - ${tool.title}`, 'info', 3000);
      },
      exportPDF(tool, group) {
        showToast(`📄 اکشن "${tool.action}" اجرا شد - ${tool.title}`, 'info', 3000);
      },
      defaultAction(tool, group) {
        showToast(`ابزار انتخاب شد: ${tool.title}`, 'info', 3000);
      }
    };

    // ============ Toast System ============
    function showToast(message, type = 'info', duration = 3000) {
      const container = document.getElementById('toastContainer');
      const toast = document.createElement('div');
      toast.className = `toast ${type}`;
      
      const icons = {
        success: '✅',
        error: '❌',
        warning: '⚠️',
        info: 'ℹ️'
      };
      
      toast.innerHTML = `
        <span>${icons[type] || 'ℹ️'}</span>
        <span>${message}</span>
      `;
      
      container.appendChild(toast);
      
      setTimeout(() => {
        toast.style.transition = 'opacity 0.3s, transform 0.3s';
        toast.style.opacity = '0';
        toast.style.transform = 'translateX(-30px)';
        setTimeout(() => toast.remove(), 300);
      }, duration);
    }

    // ============ Webhook Manager ============
    const WebhookManager = {
      getConfig() {
        return projectData.webhook;
      },

      setConfig(config) {
        projectData.webhook = { ...projectData.webhook, ...config };
        this.updateStatusUI();
      },

      isValid() {
        const cfg = this.getConfig();
        if (!cfg.enabled) return false;
        if (!cfg.url || !cfg.clientId) return false;
        if (!/^https?:\/\//i.test(cfg.url)) return false;
        return true;
      },

      updateStatusUI() {
        const cfg = this.getConfig();
        const dot = document.getElementById('webhookStatusDot');
        const text = document.getElementById('webhookStatusText');
        
        if (!dot || !text) return;
        
        dot.className = 'webhook-status-dot';
        
        if (!cfg.enabled) {
          text.textContent = 'وضعیت: غیرفعال';
        } else if (!cfg.url || !cfg.clientId) {
          dot.classList.add('pending');
          text.textContent = 'وضعیت: تنظیمات ناقص';
        } else if (!/^https?:\/\//i.test(cfg.url)) {
          dot.classList.add('error');
          text.textContent = 'وضعیت: آدرس نامعتبر';
        } else {
          dot.classList.add('success');
          text.textContent = 'وضعیت: آماده ارسال';
        }
      },

      async sendEvent(eventName, payload = {}) {
        if (!this.isValid()) {
          console.warn('[Webhook] ارسال انجام نشد - تنظیمات معتبر نیست');
          return { success: false, reason: 'invalid_config' };
        }

        const cfg = this.getConfig();
        const requestBody = {
          event: eventName,
          timestamp: new Date().toISOString(),
          projectId: projectData.projectId,
          projectName: projectData.projectName,
          projectVersion: projectData.version,
          clientId: cfg.clientId,
          data: payload
        };

        try {
          const response = await fetch(cfg.url, {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
              'X-Client-Id': cfg.clientId,
              'X-Event-Name': eventName,
              'X-Project-Id': projectData.projectId
            },
            body: JSON.stringify(requestBody)
          });

          const success = response.ok;
          if (success) {
            console.log(`[Webhook] رویداد "${eventName}" با موفقیت ارسال شد`);
          } else {
            console.error(`[Webhook] خطا در ارسال: ${response.status}`);
          }
          return { success, status: response.status };
        } catch (err) {
          console.error('[Webhook] خطای شبکه:', err);
          return { success: false, reason: 'network_error', error: err.message };
        }
      }
    };

    // ============ Tool Selection Handler ============
    function handleToolSelect(tool, group) {
      const actionHandler = ActionRegistry[tool.action] || ActionRegistry.defaultAction;
      actionHandler(tool, group);
      
      const eventPayload = {
        toolId: tool.id,
        toolTitle: tool.title,
        toolIcon: tool.icon,
        toolAction: tool.action,
        toolDescription: tool.description,
        targetEntity: tool.targetEntity,
        groupId: group.id,
        groupTitle: group.title,
        selectedAt: new Date().toISOString()
      };
      
      WebhookManager.sendEvent('tool_selected', eventPayload);
    }

    // ============ Theme Toggle ============
    const themeToggle = document.getElementById('themeToggle');
    const themeIcon = document.getElementById('themeIcon');
    const html = document.documentElement;

    const savedTheme = localStorage.getItem('theme') || 'light';
    html.setAttribute('data-theme', savedTheme);
    updateThemeUI(savedTheme);

    themeToggle.addEventListener('click', () => {
      const currentTheme = html.getAttribute('data-theme');
      const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
      html.setAttribute('data-theme', newTheme);
      localStorage.setItem('theme', newTheme);
      updateThemeUI(newTheme);
    });

    function updateThemeUI(theme) {
      themeIcon.textContent = theme === 'dark' ? '☀️' : '🌙';
    }

    // ============ Sidebar Toggle ============
    const sidebarToggle = document.getElementById('sidebarToggle');
    const sidebar = document.getElementById('sidebar');
    const sidebarIcon = document.getElementById('sidebarIcon');

    sidebarToggle.addEventListener('click', () => {
      sidebar.classList.toggle('collapsed');
      sidebarIcon.textContent = sidebar.classList.contains('collapsed') ? '☰' : '✕';
    });

    // ============ Render Tool Groups ============
    const sidebarBody = document.getElementById('sidebarBody');

    function renderToolGroups() {
      sidebarBody.innerHTML = '';
      
      projectData.toolGroups.forEach(group => {
        const groupElement = document.createElement('div');
        groupElement.className = 'sidebar-group';
        groupElement.dataset.groupId = group.id;

        const headerElement = document.createElement('div');
        headerElement.className = 'sidebar-group-header';
        
        // Add tooltip if description exists
        if (group.description) {
          headerElement.setAttribute('data-tooltip', group.description);
        }
        
        headerElement.innerHTML = `
          <div class="group-icon">
            <span>${group.icon || '📁'}</span>
            <span>${group.title}</span>
          </div>
          <span class="group-arrow">▼</span>
        `;

        const bodyElement = document.createElement('div');
        bodyElement.className = 'sidebar-group-body';

        group.tools.forEach(tool => {
          const toolBtn = document.createElement('button');
          toolBtn.className = 'sidebar-btn';
          toolBtn.dataset.toolId = tool.id;
          toolBtn.title = tool.description;
          
          toolBtn.innerHTML = `
            <span class="btn-icon-emoji">${tool.icon || '🔧'}</span>
            <span class="btn-title">${tool.title}</span>
            ${tool.shortcut ? `<span class="shortcut-badge">${tool.shortcut}</span>` : ''}
          `;
          
          toolBtn.addEventListener('click', () => {
            handleToolSelect(tool, group);
          });

          bodyElement.appendChild(toolBtn);
        });

        headerElement.addEventListener('click', () => {
          groupElement.classList.toggle('collapsed');
        });

        groupElement.appendChild(headerElement);
        groupElement.appendChild(bodyElement);
        sidebarBody.appendChild(groupElement);
      });
    }

    // ============ Settings Modal ============
    const settingsModal = document.getElementById('settingsModal');
    const saveSettingsBtn = document.getElementById('saveSettingsBtn');

    function openSettingsModal() {
      document.getElementById('settingProjectTitle').value = projectData.projectTitle;
      document.getElementById('settingVersion').value = projectData.version;
      document.getElementById('settingDescription').value = projectData.description;
      document.getElementById('settingDeveloper').value = projectData.developer;
      document.getElementById('settingCompany').value = projectData.company;
      
      document.getElementById('settingWebhookUrl').value = projectData.webhook.url;
      document.getElementById('settingWebhookClientId').value = projectData.webhook.clientId;
      document.getElementById('settingWebhookEnabled').checked = projectData.webhook.enabled;
      
      WebhookManager.updateStatusUI();
      settingsModal.classList.add('active');
    }

    function closeSettingsModal() {
      settingsModal.classList.remove('active');
    }

    document.querySelectorAll('[data-close-modal]').forEach(btn => {
      btn.addEventListener('click', closeSettingsModal);
    });

    settingsModal.addEventListener('click', (e) => {
      if (e.target === settingsModal) closeSettingsModal();
    });

    ['settingProjectTitle', 'settingVersion', 'settingDescription', 'settingDeveloper', 'settingCompany'].forEach(id => {
      const el = document.getElementById(id);
      el.addEventListener('keydown', (e) => {
        if (!e.ctrlKey && !e.metaKey && e.key.length === 1) {
          e.preventDefault();
        }
      });
      el.addEventListener('paste', (e) => e.preventDefault());
    });

    ['settingWebhookUrl', 'settingWebhookClientId', 'settingWebhookEnabled'].forEach(id => {
      document.getElementById(id).addEventListener('input', () => {
        WebhookManager.setConfig({
          url: document.getElementById('settingWebhookUrl').value,
          clientId: document.getElementById('settingWebhookClientId').value,
          enabled: document.getElementById('settingWebhookEnabled').checked
        });
      });
    });

    saveSettingsBtn.addEventListener('click', () => {
      WebhookManager.setConfig({
        url: document.getElementById('settingWebhookUrl').value.trim(),
        clientId: document.getElementById('settingWebhookClientId').value.trim(),
        enabled: document.getElementById('settingWebhookEnabled').checked
      });
      
      closeSettingsModal();
      
      showToast('تنظیمات با موفقیت ذخیره شد', 'success');
      
      WebhookManager.sendEvent('settings_updated', {
        updatedFields: ['webhook']
      });
    });

    // ============ Update Project UI ============
    function updateProjectUI() {
      document.title = projectData.projectTitle;
      document.querySelector('.app-title').textContent = projectData.projectTitle;
      document.querySelector('.app-subtitle').textContent = projectData.description;
      document.querySelector('.app-footer').innerHTML = `
        <div>
          <span class="footer-brand">طراح و توسعه‌دهنده:</span> ${projectData.developer}
          <span style="margin: 0 10px;">|</span>
          <span class="footer-brand">شرکت:</span> ${projectData.company}
        </div>
        <div>
          <span class="footer-brand">نسخه:</span> ${projectData.version}
        </div>
      `;
    }

    // ============ Keyboard Shortcuts ============
    document.addEventListener('keydown', (e) => {
      if (!(e.ctrlKey || e.metaKey)) return;
      
      const key = e.key.toLowerCase();
      
      for (const group of projectData.toolGroups) {
        const tool = group.tools.find(t => t.shortcut && 
          t.shortcut.toLowerCase().replace('ctrl+', '') === key);
        if (tool) {
          e.preventDefault();
          handleToolSelect(tool, group);
          break;
        }
      }
    });

    // ============ Initialize ============
    renderToolGroups();
    updateProjectUI();
    WebhookManager.updateStatusUI();
    
    setTimeout(() => {
      showToast(`${projectData.projectTitle} آماده استفاده است`, 'success');
    }, 500);
  </script>
</body>
</html>

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Resources\Themes\Generator\xFrameworkThemeGenerator.html

<!doctype html>
<html lang="fa" dir="rtl">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>xFramework Theme Generator</title>
    <style>
      * {
        box-sizing: border-box;
        margin: 0;
        padding: 0;
      }
      body {
        font-family: "Segoe UI", Tahoma, sans-serif;
        background: linear-gradient(135deg, #1e1e2f 0%, #2a2a3e 100%);
        color: #eee;
        min-height: 100vh;
        padding: 20px;
      }
      header {
        text-align: center;
        padding: 30px 20px;
        background: rgba(255, 255, 255, 0.05);
        border-radius: 16px;
        margin-bottom: 30px;
        backdrop-filter: blur(10px);
      }
      header h1 {
        font-size: 2.2em;
        margin-bottom: 10px;
        color: #7ec8ff;
      }
      header p {
        opacity: 0.8;
      }
      header .meta {
        margin-top: 12px;
        font-size: 0.85em;
        opacity: 0.6;
      }
      .container {
        max-width: 1500px;
        margin: 0 auto;
      }
      .section {
        background: rgba(255, 255, 255, 0.04);
        border-radius: 16px;
        padding: 24px;
        margin-bottom: 24px;
        border: 1px solid rgba(255, 255, 255, 0.08);
      }
      .section h2 {
        margin-bottom: 20px;
        color: #7ec8ff;
        border-bottom: 2px solid rgba(126, 200, 255, 0.3);
        padding-bottom: 10px;
      }

      /* Source Selection */
      .source-tabs {
        display: flex;
        gap: 8px;
        margin-bottom: 20px;
        background: rgba(0, 0, 0, 0.2);
        padding: 6px;
        border-radius: 10px;
      }
      .source-tab {
        flex: 1;
        padding: 12px 16px;
        background: rgba(255, 255, 255, 0.05);
        border-radius: 8px;
        cursor: pointer;
        text-align: center;
        font-weight: 600;
        transition: all 0.2s;
        border: 1px solid transparent;
        font-size: 14px;
      }
      .source-tab:hover {
        background: rgba(255, 255, 255, 0.1);
      }
      .source-tab.active {
        background: rgba(126, 200, 255, 0.15);
        border-color: rgba(126, 200, 255, 0.4);
        color: #7ec8ff;
      }
      .source-content {
        display: none;
      }
      .source-content.active {
        display: block;
      }

      /* Preset Themes Scrollable */
      .preset-container {
        max-height: 340px;
        overflow-y: auto;
        padding: 8px;
        background: rgba(0, 0, 0, 0.15);
        border-radius: 10px;
        border: 1px solid rgba(255, 255, 255, 0.05);
      }
      .preset-container::-webkit-scrollbar {
        width: 8px;
      }
      .preset-container::-webkit-scrollbar-track {
        background: rgba(0, 0, 0, 0.2);
        border-radius: 4px;
      }
      .preset-container::-webkit-scrollbar-thumb {
        background: rgba(126, 200, 255, 0.4);
        border-radius: 4px;
      }
      .preset-container::-webkit-scrollbar-thumb:hover {
        background: rgba(126, 200, 255, 0.6);
      }
      .preset-grid {
        display: grid;
        grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
        gap: 12px;
        padding: 4px;
      }
      .theme-card {
        background: rgba(0, 0, 0, 0.3);
        border-radius: 10px;
        padding: 12px;
        border: 2px solid transparent;
        transition: all 0.2s;
        cursor: pointer;
      }
      .theme-card:hover {
        transform: translateY(-3px);
        box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4);
        border-color: rgba(126, 200, 255, 0.3);
      }
      .theme-card.selected {
        border-color: #7ec8ff;
        box-shadow: 0 0 0 2px rgba(126, 200, 255, 0.3);
      }
      .theme-card h3 {
        margin-bottom: 8px;
        font-size: 0.95em;
        color: #cde4ff;
      }
      .color-row {
        display: flex;
        gap: 4px;
        margin-bottom: 4px;
        flex-wrap: wrap;
      }
      .color-swatch {
        width: 24px;
        height: 24px;
        border-radius: 4px;
        border: 2px solid rgba(255, 255, 255, 0.2);
      }

      /* Upload */
      .upload-section {
        background: rgba(0, 0, 0, 0.2);
        border: 2px dashed rgba(126, 200, 255, 0.3);
        border-radius: 12px;
        padding: 24px;
        text-align: center;
        transition: all 0.3s;
      }
      .upload-section:hover {
        border-color: rgba(126, 200, 255, 0.6);
        background: rgba(0, 0, 0, 0.3);
      }
      .upload-btn {
        display: inline-block;
        padding: 10px 24px;
        background: #7ec8ff;
        color: #1e1e2f;
        border-radius: 8px;
        cursor: pointer;
        font-weight: 600;
        transition: all 0.2s;
      }
      .upload-btn:hover {
        background: #5ab0f0;
        transform: translateY(-2px);
      }
      .upload-status {
        margin-top: 12px;
        font-size: 13px;
        min-height: 18px;
      }
      .upload-status.success {
        color: #4caf50;
      }
      .upload-status.error {
        color: #f44336;
      }

      /* Edit Layout */
      .edit-layout {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 20px;
      }
      @media (max-width: 1100px) {
        .edit-layout {
          grid-template-columns: 1fr;
        }
      }

      /* Preview Mode Toggle */
      .preview-mode-toggle {
        display: flex;
        gap: 6px;
        margin-bottom: 12px;
        background: rgba(0, 0, 0, 0.2);
        padding: 4px;
        border-radius: 8px;
      }
      .mode-btn {
        flex: 1;
        padding: 8px 12px;
        background: rgba(255, 255, 255, 0.05);
        border-radius: 6px;
        cursor: pointer;
        text-align: center;
        font-weight: 600;
        transition: all 0.2s;
        font-size: 13px;
        border: 1px solid transparent;
      }
      .mode-btn:hover {
        background: rgba(255, 255, 255, 0.1);
      }
      .mode-btn.active {
        background: rgba(126, 200, 255, 0.2);
        border-color: rgba(126, 200, 255, 0.4);
        color: #7ec8ff;
      }

      /* Color Inputs */
      .color-group {
        background: rgba(0, 0, 0, 0.2);
        border-radius: 10px;
        padding: 14px;
        margin-bottom: 12px;
        border: 1px solid rgba(255, 255, 255, 0.05);
      }
      .color-group-title {
        color: #7ec8ff;
        margin-bottom: 10px;
        font-size: 14px;
        padding-bottom: 6px;
        border-bottom: 1px solid rgba(126, 200, 255, 0.2);
        font-weight: 600;
      }
      .color-input-row {
        display: grid;
        grid-template-columns: 110px 40px 1fr;
        gap: 8px;
        align-items: center;
        margin-bottom: 6px;
        padding: 4px 6px;
        border-radius: 4px;
        transition: background 0.2s;
      }
      .color-input-row:hover {
        background: rgba(255, 255, 255, 0.03);
      }
      .color-input-row label {
        font-size: 12px;
        color: #aaa;
        font-weight: 500;
      }
      .color-input-row input[type="color"] {
        width: 40px;
        height: 30px;
        border: none;
        border-radius: 4px;
        cursor: pointer;
        background: transparent;
        padding: 0;
      }
      .color-input-row input[type="text"] {
        width: 100%;
        padding: 5px 8px;
        background: rgba(255, 255, 255, 0.08);
        border: 1px solid rgba(255, 255, 255, 0.15);
        border-radius: 4px;
        color: #fff;
        font-family: monospace;
        font-size: 12px;
      }

      /* Preview Box */
      .preview-box {
        background: rgba(0, 0, 0, 0.4);
        border-radius: 12px;
        padding: 16px;
        min-height: 280px;
        position: sticky;
        top: 20px;
      }
      .preview-header {
        padding: 14px;
        border-radius: 8px;
        margin-bottom: 12px;
        font-weight: bold;
        text-align: center;
        transition: all 0.3s;
      }
      .preview-buttons {
        display: flex;
        gap: 6px;
        flex-wrap: wrap;
        margin-bottom: 12px;
      }
      .preview-btn {
        padding: 8px 16px;
        border-radius: 6px;
        border: none;
        color: #fff;
        font-weight: 600;
        cursor: pointer;
        font-size: 13px;
        transition: all 0.3s;
      }
      .preview-cards {
        display: grid;
        grid-template-columns: repeat(3, 1fr);
        gap: 10px;
      }
      .preview-card {
        padding: 14px;
        border-radius: 8px;
        text-align: center;
        font-weight: bold;
        font-size: 13px;
        transition: all 0.3s;
      }
      .preview-info {
        margin-top: 12px;
        padding: 10px;
        background: rgba(255, 255, 255, 0.05);
        border-radius: 6px;
        font-size: 12px;
      }
      .preview-info strong {
        color: #7ec8ff;
      }

      /* JSON Output */
      .json-output {
        background: #0d1117;
        border-radius: 8px;
        padding: 14px;
        max-height: 380px;
        overflow: auto;
        font-family: "Consolas", monospace;
        font-size: 11px;
        color: #c9d1d9;
        border: 1px solid rgba(255, 255, 255, 0.1);
        direction: ltr;
        text-align: left;
        white-space: pre;
      }
      .json-output::-webkit-scrollbar {
        width: 8px;
        height: 8px;
      }
      .json-output::-webkit-scrollbar-track {
        background: rgba(0, 0, 0, 0.2);
      }
      .json-output::-webkit-scrollbar-thumb {
        background: rgba(126, 200, 255, 0.4);
        border-radius: 4px;
      }
      .tabs {
        display: flex;
        gap: 4px;
        margin-bottom: 12px;
      }
      .tab {
        padding: 8px 16px;
        background: rgba(255, 255, 255, 0.05);
        border-radius: 6px 6px 0 0;
        cursor: pointer;
        border: 1px solid transparent;
        font-size: 13px;
        font-weight: 500;
      }
      .tab.active {
        background: rgba(126, 200, 255, 0.15);
        border-color: rgba(126, 200, 255, 0.3);
        color: #7ec8ff;
      }

      /* Action Buttons */
      .action-buttons {
        display: flex;
        gap: 10px;
        flex-wrap: wrap;
        margin-top: 16px;
        padding-top: 16px;
        border-top: 1px solid rgba(255, 255, 255, 0.1);
      }
      .btn {
        padding: 10px 20px;
        border: none;
        border-radius: 8px;
        cursor: pointer;
        font-weight: 600;
        font-size: 13px;
        transition: all 0.2s;
      }
      .btn-primary {
        background: #7ec8ff;
        color: #1e1e2f;
      }
      .btn-primary:hover {
        background: #5ab0f0;
      }
      .btn-success {
        background: #4caf50;
        color: #fff;
      }
      .btn-success:hover {
        background: #3d9140;
      }
      .btn-warning {
        background: #ff9800;
        color: #fff;
      }
      .btn-warning:hover {
        background: #e08600;
      }
      .btn-info {
        background: #2196f3;
        color: #fff;
      }
      .btn-info:hover {
        background: #1976d2;
      }

      /* Theme Name Input */
      .theme-name-input {
        width: 100%;
        padding: 10px 14px;
        background: rgba(255, 255, 255, 0.08);
        border: 1px solid rgba(255, 255, 255, 0.15);
        border-radius: 8px;
        color: #fff;
        font-size: 14px;
        margin-bottom: 16px;
      }

      /* Normalize Info */
      .normalize-info {
        margin-top: 10px;
        padding: 10px;
        background: rgba(255, 152, 0, 0.1);
        border: 1px solid rgba(255, 152, 0, 0.3);
        border-radius: 6px;
        font-size: 12px;
        color: #ffb74d;
      }
      .normalize-info ul {
        margin: 6px 0 0 0;
        padding-right: 18px;
      }
      .normalize-info li {
        margin: 3px 0;
      }
    </style>
  </head>
  <body>
    <header>
      <h1>🎨 تولیدکننده پوسته xFramework</h1>
      <p>
        مدیریت و ویرایش پوسته‌های رنگی برای برنامه‌های کاربردی مبتنی بر
        xFramework
      </p>
      <div class="meta">
        توسعه‌دهنده: هادی خزاعی اصل | شرکت: فن آوران ساحر علم | تاریخ: ۱ شهریور
        ۱۴۰۵
      </div>
    </header>

    <div class="container">
      <div class="section">
        <h2>✏️ ویرایش پوسته</h2>

        <div class="form-group" style="margin-bottom: 16px">
          <label
            style="
              display: block;
              margin-bottom: 6px;
              color: #a8d0ff;
              font-weight: 600;
            "
            >نام پوسته</label
          >
          <input
            type="text"
            id="themeName"
            class="theme-name-input"
            value="MyCustomTheme"
          />
        </div>

        <div class="source-tabs">
          <div class="source-tab active" onclick="switchSource('preset')">
            📚 پوسته‌های از پیش تعریف‌شده (۵۰ مورد)
          </div>
          <div class="source-tab" onclick="switchSource('upload')">
            📁 بارگذاری فایل JSON
          </div>
        </div>

        <div id="source-preset" class="source-content active">
          <div class="preset-container">
            <div class="preset-grid" id="presetGrid"></div>
          </div>
        </div>

        <div id="source-upload" class="source-content">
          <div class="upload-section">
            <label for="themeFileInput" class="upload-btn"
              >📁 انتخاب فایل JSON پوسته</label
            >
            <input
              type="file"
              id="themeFileInput"
              accept=".json"
              style="display: none"
              onchange="handleThemeFileUpload(event)"
            />
            <div id="uploadStatus" class="upload-status"></div>
            <div
              id="normalizeInfo"
              class="normalize-info"
              style="display: none"
            ></div>
          </div>
        </div>

        <div style="margin-top: 20px">
          <div class="edit-layout">
            <!-- Left: Color Inputs -->
            <div>
              <div class="preview-mode-toggle">
                <div
                  class="mode-btn active"
                  onclick="switchPreviewMode('light')"
                >
                  ☀️ Light
                </div>
                <div class="mode-btn" onclick="switchPreviewMode('dark')">
                  🌙 Dark
                </div>
              </div>
              <div id="colorInputs"></div>
            </div>

            <!-- Right: Preview -->
            <div>
              <div class="preview-box" id="previewBox">
                <div class="preview-header" id="previewHeader">
                  پیش‌نمایش زنده
                </div>
                <div class="preview-buttons" id="previewButtons"></div>
                <div class="preview-cards" id="previewCards"></div>
                <div class="preview-info" id="previewInfo"></div>
              </div>
            </div>
          </div>

          <div style="margin-top: 20px">
            <div class="tabs">
              <div class="tab active" onclick="switchJsonTab('light')">
                📄 JSON (Light)
              </div>
              <div class="tab" onclick="switchJsonTab('dark')">
                📄 JSON (Dark)
              </div>
              <div class="tab" onclick="switchJsonTab('full')">
                📄 Full JSON
              </div>
            </div>
            <pre class="json-output" id="jsonOutput"></pre>
          </div>

          <div class="action-buttons">
            <button class="btn btn-success" onclick="downloadTheme()">
              ⬇️ دانلود JSON
            </button>
            <button class="btn btn-warning" onclick="copyJson()">
              📋 کپی JSON
            </button>
            <button class="btn btn-info" onclick="resetToEmpty()">
              🔄 بازنشانی
            </button>
          </div>
        </div>
      </div>
    </div>

    <script>
      // ============ 50 Preset Themes ============
      const PRESET_THEMES = [
        { name: "default", p: "#3880ff", s: "#0cd1e8", t: "#7044ff" },
        { name: "blue", p: "#3880ff", s: "#0cd1e8", t: "#7044ff" },
        { name: "green", p: "#0f9535", s: "#6d950f", t: "#086fa7" },
        { name: "brown", p: "#5d4037", s: "#512da8", t: "#a1887f" },
        { name: "orange", p: "#d88e30", s: "#c83adf", t: "#d3dd33" },
        { name: "royal-purple", p: "#6a0dad", s: "#9b59b6", t: "#8e44ad" },
        { name: "teal-ocean", p: "#008080", s: "#20b2aa", t: "#48d1cc" },
        { name: "rose-garden", p: "#e91e63", s: "#f06292", t: "#c2185b" },
        { name: "midnight-blue", p: "#191970", s: "#4169e1", t: "#6495ed" },
        { name: "coral-sunset", p: "#ff7f50", s: "#ffa07a", t: "#ff6347" },
        { name: "forest-green", p: "#228b22", s: "#32cd32", t: "#006400" },
        { name: "sunset-orange", p: "#ff4500", s: "#ffa500", t: "#ffd700" },
        { name: "ocean-blue", p: "#006994", s: "#4682b4", t: "#5f9ea0" },
        { name: "cherry-blossom", p: "#ff69b4", s: "#ffb6c1", t: "#ff1493" },
        { name: "emerald", p: "#50c878", s: "#2ecc71", t: "#27ae60" },
        { name: "lavender", p: "#e6e6fa", s: "#b19cd9", t: "#9370db" },
        { name: "mint-green", p: "#98ff98", s: "#3eb489", t: "#00a550" },
        { name: "peach", p: "#ffdab9", s: "#ffcc99", t: "#ff9966" },
        { name: "sky-blue", p: "#87ceeb", s: "#87cefa", t: "#00bfff" },
        { name: "olive", p: "#808000", s: "#6b8e23", t: "#556b2f" },
        { name: "burgundy", p: "#800020", s: "#722f37", t: "#8b0000" },
        { name: "navy", p: "#000080", s: "#00008b", t: "#0000cd" },
        { name: "mustard", p: "#ffdb58", s: "#ffdb58", t: "#e1ad01" },
        { name: "sage", p: "#bcb88a", s: "#9dc183", t: "#6b8e23" },
        { name: "plum", p: "#8e4585", s: "#673147", t: "#4b0082" },
        { name: "turquoise", p: "#40e0d0", s: "#00ced1", t: "#00ced1" },
        { name: "maroon", p: "#800000", s: "#8b0000", t: "#a52a2a" },
        { name: "slate", p: "#708090", s: "#778899", t: "#2f4f4f" },
        { name: "gold", p: "#ffd700", s: "#daa520", t: "#b8860b" },
        { name: "silver", p: "#c0c0c0", s: "#a9a9a9", t: "#808080" },
        { name: "rust", p: "#b7410e", s: "#a52a2a", t: "#8b4513" },
        { name: "indigo", p: "#4b0082", s: "#483d8b", t: "#191970" },
        { name: "periwinkle", p: "#ccccff", s: "#ccccff", t: "#9999ff" },
        { name: "salmon", p: "#fa8072", s: "#ffa07a", t: "#e9967a" },
        { name: "aquamarine", p: "#7fffd4", s: "#7fffd4", t: "#00ffff" },
        { name: "crimson", p: "#dc143c", s: "#dc143c", t: "#b22222" },
        { name: "cobalt", p: "#0047ab", s: "#0047ab", t: "#00008b" },
        { name: "bronze", p: "#cd7f32", s: "#cd7f32", t: "#8b4513" },
        { name: "magenta", p: "#ff00ff", s: "#ff00ff", t: "#c71585" },
        { name: "cyan", p: "#00ffff", s: "#00ffff", t: "#00ced1" },
        { name: "amber", p: "#ffbf00", s: "#ffbf00", t: "#ff8c00" },
        { name: "ivory", p: "#fffff0", s: "#fffff0", t: "#fafad2" },
        { name: "charcoal", p: "#36454f", s: "#36454f", t: "#2f4f4f" },
        { name: "lime", p: "#00ff00", s: "#32cd32", t: "#00ff00" },
        { name: "fuchsia", p: "#ff00ff", s: "#ff00ff", t: "#c71585" },
        { name: "taupe", p: "#483c32", s: "#483c32", t: "#483c32" },
        { name: "mauve", p: "#e0b0ff", s: "#e0b0ff", t: "#dda0dd" },
        { name: "sienna", p: "#a0522d", s: "#a0522d", t: "#8b4513" },
        { name: "peridot", p: "#e6e200", s: "#e6e200", t: "#c8b800" },
        { name: "amethyst", p: "#9966cc", s: "#9966cc", t: "#8a2be2" },
      ];

      // ============ Color Utilities ============
      function hexToRgb(hex) {
        hex = hex.trim().replace("#", "");
        if (hex.length === 8) hex = hex.substring(0, 6);
        if (hex.length === 3)
          hex = hex
            .split("")
            .map((c) => c + c)
            .join("");
        const r = parseInt(hex.slice(0, 2), 16);
        const g = parseInt(hex.slice(2, 4), 16);
        const b = parseInt(hex.slice(4, 6), 16);
        return { r, g, b };
      }
      function rgbToHex(r, g, b) {
        return (
          "#" +
          [r, g, b]
            .map((x) => {
              const hex = Math.max(0, Math.min(255, Math.round(x))).toString(
                16,
              );
              return hex.length === 1 ? "0" + hex : hex;
            })
            .join("")
        );
      }
      function rgbToHsl(r, g, b) {
        r /= 255;
        g /= 255;
        b /= 255;
        const max = Math.max(r, g, b),
          min = Math.min(r, g, b);
        let h,
          s,
          l = (max + min) / 2;
        if (max === min) {
          h = s = 0;
        } else {
          const d = max - min;
          s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
          switch (max) {
            case r:
              h = (g - b) / d + (g < b ? 6 : 0);
              break;
            case g:
              h = (b - r) / d + 2;
              break;
            case b:
              h = (r - g) / d + 4;
              break;
          }
          h /= 6;
        }
        return { h: h * 360, s: s * 100, l: l * 100 };
      }
      function hslToRgb(h, s, l) {
        h /= 360;
        s /= 100;
        l /= 100;
        let r, g, b;
        if (s === 0) {
          r = g = b = l;
        } else {
          const hue2rgb = (p, q, t) => {
            if (t < 0) t += 1;
            if (t > 1) t -= 1;
            if (t < 1 / 6) return p + (q - p) * 6 * t;
            if (t < 1 / 2) return q;
            if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
            return p;
          };
          const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
          const p = 2 * l - q;
          r = hue2rgb(p, q, h + 1 / 3);
          g = hue2rgb(p, q, h);
          b = hue2rgb(p, q, h - 1 / 3);
        }
        return {
          r: Math.round(r * 255),
          g: Math.round(g * 255),
          b: Math.round(b * 255),
        };
      }
      function generateShade(hex, percent = 18) {
        const rgb = hexToRgb(hex);
        const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b);
        hsl.l = Math.max(0, hsl.l - percent);
        hsl.s = Math.min(100, hsl.s + 5);
        const newRgb = hslToRgb(hsl.h, hsl.s, hsl.l);
        return rgbToHex(newRgb.r, newRgb.g, newRgb.b);
      }
      function generateTint(hex, percent = 18) {
        const rgb = hexToRgb(hex);
        const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b);
        hsl.l = Math.min(100, hsl.l + percent);
        hsl.s = Math.max(0, hsl.s - 5);
        const newRgb = hslToRgb(hsl.h, hsl.s, hsl.l);
        return rgbToHex(newRgb.r, newRgb.g, newRgb.b);
      }
      function generateContrast(hex) {
        const rgb = hexToRgb(hex);
        const luminance = (0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b) / 255;
        return luminance > 0.55 ? "#000000" : "#ffffff";
      }
      function normalizeColorValue(value) {
        if (typeof value !== "string") return value;
        let normalized = value.trim();
        if (/^#[0-9A-Fa-f]{8}$/.test(normalized))
          normalized = normalized.substring(0, 7);
        if (normalized && !normalized.startsWith("#"))
          normalized = "#" + normalized;
        if (!/^#[0-9A-Fa-f]{6}$/.test(normalized)) return value;
        return normalized;
      }

      // ============ Build Theme ============
      function buildTheme(name, primary, secondary, tertiary) {
        const success = "#10dc60",
          warning = "#ffce00",
          danger = "#f04141";
        const dark = "#222428",
          medium = "#989aa2",
          light = "#f4f5f8";
        return {
          name,
          light: {
            type: "light",
            colors: {
              XColorPrimary: primary,
              XColorPrimaryContrast: generateContrast(primary),
              XColorPrimaryShade: generateShade(primary),
              XColorPrimaryShadeContrast: generateContrast(
                generateShade(primary),
              ),
              XColorPrimaryTint: generateTint(primary),
              XColorPrimaryTintContrast: generateContrast(
                generateTint(primary),
              ),
              XColorSecondary: secondary,
              XColorSecondaryContrast: generateContrast(secondary),
              XColorSecondaryShade: generateShade(secondary),
              XColorSecondaryShadeContrast: generateContrast(
                generateShade(secondary),
              ),
              XColorSecondaryTint: generateTint(secondary),
              XColorSecondaryTintContrast: generateContrast(
                generateTint(secondary),
              ),
              XColorTertiary: tertiary,
              XColorTertiaryContrast: generateContrast(tertiary),
              XColorTertiaryShade: generateShade(tertiary),
              XColorTertiaryShadeContrast: generateContrast(
                generateShade(tertiary),
              ),
              XColorTertiaryTint: generateTint(tertiary),
              XColorTertiaryTintContrast: generateContrast(
                generateTint(tertiary),
              ),
              XColorSuccess: success,
              XColorSuccessContrast: "#ffffff",
              XColorSuccessShade: generateShade(success),
              XColorSuccessShadeContrast: "#ffffff",
              XColorSuccessTint: generateTint(success),
              XColorSuccessTintContrast: "#ffffff",
              XColorWarning: warning,
              XColorWarningContrast: "#ffffff",
              XColorWarningShade: generateShade(warning),
              XColorWarningShadeContrast: "#ffffff",
              XColorWarningTint: generateTint(warning),
              XColorWarningTintContrast: "#ffffff",
              XColorDanger: danger,
              XColorDangerContrast: "#ffffff",
              XColorDangerShade: generateShade(danger),
              XColorDangerShadeContrast: "#ffffff",
              XColorDangerTint: generateTint(danger),
              XColorDangerTintContrast: "#ffffff",
              XColorDark: dark,
              XColorDarkContrast: "#ffffff",
              XColorDarkShade: generateShade(dark),
              XColorDarkShadeContrast: "#ffffff",
              XColorDarkTint: generateTint(dark),
              XColorDarkTintContrast: "#ffffff",
              XColorMedium: medium,
              XColorMediumContrast: "#ffffff",
              XColorMediumShade: generateShade(medium),
              XColorMediumShadeContrast: "#ffffff",
              XColorMediumTint: generateTint(medium),
              XColorMediumTintContrast: "#ffffff",
              XColorLight: light,
              XColorLightContrast: "#000000",
              XColorLightShade: generateShade(light),
              XColorLightShadeContrast: "#000000",
              XColorLightTint: generateTint(light),
              XColorLightTintContrast: "#000000",
            },
          },
          dark: {
            type: "dark",
            colors: {
              XColorPrimary: dark,
              XColorPrimaryContrast: "#ffffff",
              XColorPrimaryShade: generateShade(dark),
              XColorPrimaryShadeContrast: "#ffffff",
              XColorPrimaryTint: generateTint(dark),
              XColorPrimaryTintContrast: "#ffffff",
              XColorSecondary: generateShade(dark, 5),
              XColorSecondaryContrast: "#ffffff",
              XColorSecondaryShade: generateShade(dark, 10),
              XColorSecondaryShadeContrast: "#ffffff",
              XColorSecondaryTint: generateTint(dark, 10),
              XColorSecondaryTintContrast: "#ffffff",
              XColorTertiary: tertiary,
              XColorTertiaryContrast: generateContrast(tertiary),
              XColorTertiaryShade: generateShade(tertiary),
              XColorTertiaryShadeContrast: generateContrast(
                generateShade(tertiary),
              ),
              XColorTertiaryTint: generateTint(tertiary),
              XColorTertiaryTintContrast: generateContrast(
                generateTint(tertiary),
              ),
              XColorSuccess: success,
              XColorSuccessContrast: "#ffffff",
              XColorSuccessShade: generateShade(success),
              XColorSuccessShadeContrast: "#ffffff",
              XColorSuccessTint: generateTint(success),
              XColorSuccessTintContrast: "#ffffff",
              XColorWarning: warning,
              XColorWarningContrast: "#ffffff",
              XColorWarningShade: generateShade(warning),
              XColorWarningShadeContrast: "#ffffff",
              XColorWarningTint: generateTint(warning),
              XColorWarningTintContrast: "#ffffff",
              XColorDanger: danger,
              XColorDangerContrast: "#ffffff",
              XColorDangerShade: generateShade(danger),
              XColorDangerShadeContrast: "#ffffff",
              XColorDangerTint: generateTint(danger),
              XColorDangerTintContrast: "#ffffff",
              XColorDark: dark,
              XColorDarkContrast: "#ffffff",
              XColorDarkShade: generateShade(dark),
              XColorDarkShadeContrast: "#ffffff",
              XColorDarkTint: generateTint(dark),
              XColorDarkTintContrast: "#ffffff",
              XColorMedium: medium,
              XColorMediumContrast: "#ffffff",
              XColorMediumShade: generateShade(medium),
              XColorMediumShadeContrast: "#ffffff",
              XColorMediumTint: generateTint(medium),
              XColorMediumTintContrast: "#ffffff",
              XColorLight: light,
              XColorLightContrast: "#000000",
              XColorLightShade: generateShade(light),
              XColorLightShadeContrast: "#000000",
              XColorLightTint: generateTint(light),
              XColorLightTintContrast: "#000000",
            },
          },
        };
      }

      // ============ State ============
      let currentTheme = null;
      let previewMode = "light"; // 'light' or 'dark'
      let jsonTab = "light";
      let selectedPresetIndex = -1;

      // ============ Render Presets ============
      function renderPresets() {
        const grid = document.getElementById("presetGrid");
        grid.innerHTML = "";
        PRESET_THEMES.forEach((preset, idx) => {
          const theme = buildTheme(preset.name, preset.p, preset.s, preset.t);
          const card = document.createElement("div");
          card.className = "theme-card";
          card.dataset.index = idx;
          card.innerHTML = `
      <h3>${preset.name}</h3>
      <div class="color-row">
        <div class="color-swatch" style="background:${theme.light.colors.XColorPrimary}"></div>
        <div class="color-swatch" style="background:${theme.light.colors.XColorSecondary}"></div>
        <div class="color-swatch" style="background:${theme.light.colors.XColorTertiary}"></div>
      </div>
      <div class="color-row">
        <div class="color-swatch" style="background:${theme.light.colors.XColorSuccess}"></div>
        <div class="color-swatch" style="background:${theme.light.colors.XColorWarning}"></div>
        <div class="color-swatch" style="background:${theme.light.colors.XColorDanger}"></div>
      </div>
    `;
          card.onclick = () => selectPreset(idx);
          grid.appendChild(card);
        });
      }

      function selectPreset(idx) {
        selectedPresetIndex = idx;
        document
          .querySelectorAll(".theme-card")
          .forEach((c) => c.classList.remove("selected"));
        const card = document.querySelector(`.theme-card[data-index="${idx}"]`);
        if (card) card.classList.add("selected");

        const preset = PRESET_THEMES[idx];
        const theme = buildTheme(preset.name, preset.p, preset.s, preset.t);
        currentTheme = theme;

        document.getElementById("themeName").value = preset.name;
        renderColorInputs();
        renderPreview();
        renderJson();
      }

      // ============ Render Color Inputs ============
      function renderColorInputs() {
        const container = document.getElementById("colorInputs");
        if (!currentTheme) {
          container.innerHTML =
            '<p style="opacity:0.5; text-align:center; padding:20px;">یک پوسته انتخاب کنید یا فایل JSON بارگذاری کنید</p>';
          return;
        }

        const mode = previewMode;
        const colors = currentTheme[mode].colors;
        container.innerHTML = "";

        const groups = [
          "Primary",
          "Secondary",
          "Tertiary",
          "Success",
          "Warning",
          "Danger",
          "Dark",
          "Medium",
          "Light",
        ];
        const groupLabels = {
          Primary: "رنگ اصلی (Primary)",
          Secondary: "رنگ دوم (Secondary)",
          Tertiary: "رنگ سوم (Tertiary)",
          Success: "موفقیت (Success)",
          Warning: "هشدار (Warning)",
          Danger: "خطر (Danger)",
          Dark: "تیره (Dark)",
          Medium: "میانه (Medium)",
          Light: "روشن (Light)",
        };

        groups.forEach((group) => {
          const groupDiv = document.createElement("div");
          groupDiv.className = "color-group";

          let html = `<div class="color-group-title">${groupLabels[group]}</div>`;

          const fields = [
            { key: `XColor${group}`, label: "Base" },
            { key: `XColor${group}Shade`, label: "Shade" },
            { key: `XColor${group}Tint`, label: "Tint" },
            { key: `XColor${group}Contrast`, label: "Contrast" },
            { key: `XColor${group}ShadeContrast`, label: "Shade Contrast" },
            { key: `XColor${group}TintContrast`, label: "Tint Contrast" },
          ];

          fields.forEach((field) => {
            const value = colors[field.key] || "#000000";
            html += `
        <div class="color-input-row">
          <label>${field.label}</label>
          <input type="color" value="${value}" data-key="${field.key}" data-mode="${mode}" onchange="handleColorChange(this)">
          <input type="text" value="${value}" data-key="${field.key}" data-mode="${mode}" onchange="handleColorChange(this)" oninput="handleColorInput(this)">
        </div>
      `;
          });

          groupDiv.innerHTML = html;
          container.appendChild(groupDiv);
        });
      }

      function handleColorChange(input) {
        const row = input.parentElement;
        const colorInput = row.querySelector('input[type="color"]');
        const textInput = row.querySelector('input[type="text"]');

        if (input.type === "color") {
          textInput.value = input.value;
        } else {
          if (/^#[0-9A-Fa-f]{6}$/.test(input.value)) {
            colorInput.value = input.value;
          } else {
            return;
          }
        }

        const key = input.dataset.key;
        const mode = input.dataset.mode;
        if (currentTheme && currentTheme[mode]) {
          currentTheme[mode].colors[key] =
            input.type === "color" ? input.value : input.value;
        }

        renderPreview();
        renderJson();
      }

      function handleColorInput(input) {
        if (/^#[0-9A-Fa-f]{6}$/.test(input.value)) {
          handleColorChange(input);
        }
      }

      // ============ Preview Mode Toggle ============
      function switchPreviewMode(mode) {
        previewMode = mode;
        document
          .querySelectorAll(".mode-btn")
          .forEach((b) => b.classList.remove("active"));
        event.target.classList.add("active");
        renderColorInputs();
        renderPreview();
      }

      // ============ Render Preview ============
      function renderPreview() {
        if (!currentTheme) return;

        const mode = previewMode;
        const colors = currentTheme[mode].colors;

        const header = document.getElementById("previewHeader");
        header.style.background = colors.XColorPrimary;
        header.style.color = colors.XColorPrimaryContrast;
        header.textContent = `پیش‌نمایش پوسته: ${currentTheme.name} (${mode === "light" ? "روشن" : "تیره"})`;

        const buttons = document.getElementById("previewButtons");
        buttons.innerHTML = `
    <button class="preview-btn" style="background:${colors.XColorPrimary};color:${colors.XColorPrimaryContrast}">Primary</button>
    <button class="preview-btn" style="background:${colors.XColorSecondary};color:${colors.XColorSecondaryContrast}">Secondary</button>
    <button class="preview-btn" style="background:${colors.XColorTertiary};color:${colors.XColorTertiaryContrast}">Tertiary</button>
    <button class="preview-btn" style="background:${colors.XColorSuccess};color:${colors.XColorSuccessContrast}">Success</button>
    <button class="preview-btn" style="background:${colors.XColorWarning};color:${colors.XColorWarningContrast}">Warning</button>
    <button class="preview-btn" style="background:${colors.XColorDanger};color:${colors.XColorDangerContrast}">Danger</button>
  `;

        const cards = document.getElementById("previewCards");
        cards.innerHTML = `
    <div class="preview-card" style="background:${colors.XColorPrimaryTint};color:${colors.XColorPrimaryTintContrast}">Tint</div>
    <div class="preview-card" style="background:${colors.XColorPrimary};color:${colors.XColorPrimaryContrast}">Base</div>
    <div class="preview-card" style="background:${colors.XColorPrimaryShade};color:${colors.XColorPrimaryShadeContrast}">Shade</div>
  `;

        const info = document.getElementById("previewInfo");
        info.innerHTML = `
    <div><strong>نام پوسته:</strong> ${currentTheme.name}</div>
    <div><strong>حالت نمایش:</strong> ${mode === "light" ? "روشن (Light)" : "تیره (Dark)"}</div>
    <div><strong>Primary:</strong> <span style="display:inline-block;width:14px;height:14px;background:${colors.XColorPrimary};border-radius:3px;vertical-align:middle;border:1px solid rgba(255,255,255,0.3)"></span> ${colors.XColorPrimary}</div>
    <div><strong>Secondary:</strong> <span style="display:inline-block;width:14px;height:14px;background:${colors.XColorSecondary};border-radius:3px;vertical-align:middle;border:1px solid rgba(255,255,255,0.3)"></span> ${colors.XColorSecondary}</div>
  `;
      }

      // ============ JSON Tab ============
      function switchJsonTab(tab) {
        jsonTab = tab;
        document
          .querySelectorAll(".tab")
          .forEach((t) => t.classList.remove("active"));
        event.target.classList.add("active");
        renderJson();
      }

      function renderJson() {
        if (!currentTheme) {
          document.getElementById("jsonOutput").textContent = "";
          return;
        }
        let data;
        if (jsonTab === "light") data = currentTheme.light;
        else if (jsonTab === "dark") data = currentTheme.dark;
        else data = currentTheme;
        document.getElementById("jsonOutput").textContent = JSON.stringify(
          data,
          null,
          1,
        );
      }

      // ============ Source Tabs ============
      function switchSource(source) {
        document
          .querySelectorAll(".source-tab")
          .forEach((t) => t.classList.remove("active"));
        event.target.classList.add("active");
        document
          .querySelectorAll(".source-content")
          .forEach((c) => c.classList.remove("active"));
        document.getElementById(`source-${source}`).classList.add("active");
      }

      // ============ File Upload ============
      function handleThemeFileUpload(event) {
        const file = event.target.files[0];
        if (!file) return;

        const reader = new FileReader();
        reader.onload = function (e) {
          try {
            let raw = JSON.parse(e.target.result);

            // Normalize
            const { data: normalized, changes } = normalizeThemeJson(raw);

            const normalizeInfo = document.getElementById("normalizeInfo");
            if (
              changes.keysTrimmed.length > 0 ||
              changes.alphaRemoved.length > 0
            ) {
              let info = "<strong>⚠️ فایل قدیمی شناسایی و اصلاح شد:</strong>";
              if (changes.keysTrimmed.length > 0) {
                info += "<br><strong>کلیدهای اصلاح‌شده:</strong><ul>";
                changes.keysTrimmed
                  .slice(0, 5)
                  .forEach((c) => (info += `<li>${c}</li>`));
                if (changes.keysTrimmed.length > 5)
                  info += `<li>... و ${changes.keysTrimmed.length - 5} مورد دیگر</li>`;
                info += "</ul>";
              }
              if (changes.alphaRemoved.length > 0) {
                info +=
                  "<br><strong>رنگ‌های اصلاح‌شده (حذف Alpha):</strong><ul>";
                changes.alphaRemoved
                  .slice(0, 5)
                  .forEach((c) => (info += `<li>${c}</li>`));
                if (changes.alphaRemoved.length > 5)
                  info += `<li>... و ${changes.alphaRemoved.length - 5} مورد دیگر</li>`;
                info += "</ul>";
              }
              normalizeInfo.innerHTML = info;
              normalizeInfo.style.display = "block";
            } else {
              normalizeInfo.style.display = "none";
            }

            if (
              !normalized.name ||
              !normalized.light ||
              !normalized.dark ||
              !normalized.light.colors ||
              !normalized.dark.colors
            ) {
              throw new Error("ساختار فایل JSON نامعتبر است");
            }

            currentTheme = normalized;
            selectedPresetIndex = -1;
            document
              .querySelectorAll(".theme-card")
              .forEach((c) => c.classList.remove("selected"));

            document.getElementById("themeName").value = normalized.name;
            document.getElementById("uploadStatus").className =
              "upload-status success";
            document.getElementById("uploadStatus").textContent =
              `✅ فایل با موفقیت بارگذاری شد: ${normalized.name}`;

            renderColorInputs();
            renderPreview();
            renderJson();
          } catch (err) {
            document.getElementById("uploadStatus").className =
              "upload-status error";
            document.getElementById("uploadStatus").textContent =
              `❌ خطا: ${err.message}`;
            document.getElementById("normalizeInfo").style.display = "none";
          }
        };
        reader.readAsText(file);
      }

      function normalizeThemeJson(themeData) {
        const changes = { keysTrimmed: [], alphaRemoved: [] };
        if (!themeData || typeof themeData !== "object")
          return { data: themeData, changes };

        const result = {};
        for (const key of Object.keys(themeData)) {
          const nk = key.trim();
          if (nk !== key) changes.keysTrimmed.push(`"${key}" → "${nk}"`);

          const value = themeData[key];
          if (nk === "colors" && typeof value === "object" && value !== null) {
            const nc = {};
            for (const ck of Object.keys(value)) {
              const nck = ck.trim();
              if (nck !== ck) changes.keysTrimmed.push(`"${ck}" → "${nck}"`);
              const cv = value[ck];
              if (typeof cv === "string") {
                const orig = cv;
                const ncv = normalizeColorValue(cv);
                if (ncv !== orig) {
                  changes.alphaRemoved.push(`${nck}: "${orig}" → "${ncv}"`);
                }
                nc[nck] = ncv;
              } else {
                nc[nck] = cv;
              }
            }
            result[nk] = nc;
          } else if (typeof value === "object" && value !== null) {
            const sub = normalizeThemeJson(value);
            result[nk] = sub.data;
            changes.keysTrimmed.push(...sub.changes.keysTrimmed);
            changes.alphaRemoved.push(...sub.changes.alphaRemoved);
          } else {
            result[nk] = value;
          }
        }
        return { data: result, changes };
      }

      // ============ Actions ============
      function downloadTheme() {
        if (!currentTheme) {
          alert("ابتدا یک پوسته انتخاب کنید");
          return;
        }
        const blob = new Blob([JSON.stringify(currentTheme, null, 1)], {
          type: "application/json",
        });
        const url = URL.createObjectURL(blob);
        const a = document.createElement("a");
        a.href = url;
        a.download = `x-framework-${currentTheme.name}-theme.json`;
        a.click();
        URL.revokeObjectURL(url);
      }

      function copyJson() {
        if (!currentTheme) {
          alert("ابتدا یک پوسته انتخاب کنید");
          return;
        }
        let data;
        if (jsonTab === "light") data = currentTheme.light;
        else if (jsonTab === "dark") data = currentTheme.dark;
        else data = currentTheme;
        navigator.clipboard
          .writeText(JSON.stringify(data, null, 1))
          .then(() => alert("✅ JSON در کلیپ‌بورد کپی شد"));
      }

      function resetToEmpty() {
        currentTheme = null;
        selectedPresetIndex = -1;
        document
          .querySelectorAll(".theme-card")
          .forEach((c) => c.classList.remove("selected"));
        document.getElementById("themeName").value = "MyCustomTheme";
        document.getElementById("uploadStatus").textContent = "";
        document.getElementById("normalizeInfo").style.display = "none";
        document.getElementById("themeFileInput").value = "";
        renderColorInputs();
        renderPreview();
        renderJson();
      }

      // ============ Init ============
      renderPresets();
      renderColorInputs();
      renderPreview();
      renderJson();

      // Select first preset by default
      selectPreset(0);
    </script>
  </body>
</html>

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Resources\Tools\xKanban.html

<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>xKanban | ابزار مدیریت بردهای کانبان</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap');

/* ============ Theme Variables ============ */
:root {
  --primary: #1a365d;
  --primary-light: #2c5282;
  --primary-dark: #0f2341;
  --accent: #c9a961;
  --accent-dark: #a8893f;
  --accent-light: #e0c789;
  --bg: #f5f7fa;
  --board-bg: #e2e8f0;
  --card-bg: #ffffff;
  --column-bg: #edf2f7;
  --panel-bg: #ffffff;
  --text: #1a202c;
  --text-muted: #4a5568;
  --text-light: #718096;
  --border: #cbd5e0;
  --border-light: #e2e8f0;
  --success: #38a169;
  --danger: #e53e3e;
  --warning: #dd6b20;
  --info: #3182ce;
  --shadow: 0 4px 6px rgba(0,0,0,0.08), 0 10px 20px rgba(26,54,93,0.1);
  --shadow-sm: 0 2px 4px rgba(0,0,0,0.06);
  --shadow-lg: 0 10px 30px rgba(0,0,0,0.15);
  --radius: 10px;
  --radius-sm: 6px;
  --sidebar-width: 280px;
  --header-height: 64px;
  --tabs-height: 48px;
  --footer-height: 36px;
}

[data-theme="dark"] {
  --primary: #2c5282;
  --primary-light: #3b6fb5;
  --primary-dark: #1a365d;
  --accent: #e0c789;
  --accent-dark: #c9a961;
  --accent-light: #f0d9a0;
  --bg: #1a202c;
  --board-bg: #2d3748;
  --card-bg: #2d3748;
  --column-bg: #374151;
  --panel-bg: #1f2937;
  --text: #e2e8f0;
  --text-muted: #cbd5e0;
  --text-light: #a0aec0;
  --border: #4a5568;
  --border-light: #374151;
  --shadow: 0 4px 6px rgba(0,0,0,0.3), 0 10px 20px rgba(0,0,0,0.4);
  --shadow-sm: 0 2px 4px rgba(0,0,0,0.2);
  --shadow-lg: 0 10px 30px rgba(0,0,0,0.5);
}

* { box-sizing: border-box; margin: 0; padding: 0; }

body {
  font-family: 'Vazirmatn', Tahoma, sans-serif;
  background: var(--bg);
  color: var(--text);
  height: 100vh;
  overflow: hidden;
  font-size: 14px;
  display: flex;
  flex-direction: column;
  transition: background 0.3s, color 0.3s;
}

/* ============ Header ============ */
.app-header {
  background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);
  color: white;
  padding: 0 20px;
  height: var(--header-height);
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 3px solid var(--accent);
  box-shadow: var(--shadow);
  z-index: 100;
  flex-shrink: 0;
  position: relative;
  overflow: hidden;
}

.app-header::before {
  content: '';
  position: absolute;
  top: -50%;
  left: -10%;
  width: 300px;
  height: 300px;
  background: radial-gradient(circle, rgba(201,169,97,0.15) 0%, transparent 70%);
  border-radius: 50%;
  pointer-events: none;
}

.app-brand {
  display: flex;
  align-items: center;
  gap: 14px;
  position: relative;
  z-index: 1;
}

.app-logo {
  width: 44px; height: 44px;
  background: var(--accent);
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 22px;
  font-weight: 800;
  color: var(--primary-dark);
  box-shadow: 0 4px 12px rgba(0,0,0,0.25);
  flex-shrink: 0;
}

.app-brand-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.app-title {
  font-size: 16px;
  font-weight: 800;
  letter-spacing: -0.3px;
  color: white;
}

.app-subtitle {
  font-size: 11px;
  opacity: 0.85;
  color: rgba(255,255,255,0.9);
}

.header-actions {
  display: flex;
  gap: 8px;
  align-items: center;
  position: relative;
  z-index: 1;
}

/* ============ Buttons ============ */
.btn {
  background: linear-gradient(135deg, var(--primary-light), var(--primary));
  color: white;
  border: none;
  padding: 8px 14px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  transition: all 0.2s;
  box-shadow: 0 2px 4px rgba(0,0,0,0.15);
  white-space: nowrap;
}

.btn:hover { transform: translateY(-1px); box-shadow: 0 4px 8px rgba(0,0,0,0.2); filter: brightness(1.1); }
.btn:active { transform: translateY(0); }
.btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }

.btn-accent {
  background: linear-gradient(135deg, var(--accent), var(--accent-dark));
  color: var(--primary-dark);
  font-weight: 700;
}

.btn-success { background: linear-gradient(135deg, #48bb78, #38a169); color: white; }
.btn-danger { background: linear-gradient(135deg, #f56565, #e53e3e); color: white; }
.btn-warning { background: linear-gradient(135deg, #ed8936, #dd6b20); color: white; }
.btn-info { background: linear-gradient(135deg, #4299e1, #3182ce); color: white; }

.btn-ghost {
  background: rgba(26,54,93,0.08);
  border: 1px solid var(--border);
  color: var(--primary);
  font-weight: 600;
}
.btn-ghost:hover { 
  background: rgba(26,54,93,0.15);
  border-color: var(--primary-light);
}

[data-theme="dark"] .btn-ghost {
  background: rgba(255,255,255,0.1);
  border: 1px solid rgba(255,255,255,0.25);
  color: white;
}
[data-theme="dark"] .btn-ghost:hover { 
  background: rgba(255,255,255,0.2);
}

.btn-sm { padding: 5px 10px; font-size: 12px; }
.btn-xs { padding: 3px 7px; font-size: 11px; }

/* ============ Main Layout ============ */
.app-main {
  flex: 1;
  display: flex;
  overflow: hidden;
  position: relative;
  flex-direction: row;
}

/* ============ Sidebar ============ */
.sidebar {
  order: -1;
  width: var(--sidebar-width);
  background: var(--panel-bg);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  transition: width 0.3s ease;
  box-shadow: var(--shadow);
  overflow: hidden;
  flex-shrink: 0;
  z-index: 50;
}

.sidebar.collapsed {
  width: 0;
  overflow: hidden;
}

.sidebar-header {
  background: linear-gradient(135deg, var(--primary), var(--primary-light));
  color: white;
  padding: 12px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 2px solid var(--accent);
  min-height: 48px;
}

.sidebar-title {
  font-size: 13px;
  font-weight: 700;
  display: flex;
  align-items: center;
  gap: 8px;
  white-space: nowrap;
  overflow: hidden;
}

.sidebar.collapsed .sidebar-title { display: none; }

.sidebar-body {
  flex: 1;
  overflow-y: auto;
  padding: 8px;
}

.sidebar.collapsed .sidebar-body { display: none; }

.sidebar-group {
  margin-bottom: 6px;
  border: 1px solid var(--border-light);
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--card-bg);
}

.sidebar-group-header {
  background: linear-gradient(135deg, var(--primary), var(--primary-light));
  color: white;
  padding: 10px 12px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 13px;
  font-weight: 700;
  user-select: none;
  transition: all 0.2s;
}

.sidebar-group-header:hover { filter: brightness(1.1); }

.sidebar-group-header .group-icon {
  display: flex;
  align-items: center;
  gap: 8px;
}

.sidebar-group-header .group-arrow {
  transition: transform 0.3s;
  font-size: 12px;
}

.sidebar-group.collapsed .group-arrow { transform: rotate(90deg); }
.sidebar-group.collapsed .sidebar-group-body { display: none; }

.sidebar-group-body {
  padding: 6px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.sidebar-btn {
  background: var(--card-bg);
  border: 1px solid var(--border-light);
  color: var(--text);
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-family: inherit;
  font-size: 12px;
  font-weight: 500;
  display: flex;
  align-items: center;
  gap: 8px;
  transition: all 0.2s;
  text-align: right;
  width: 100%;
}

.sidebar-btn:hover {
  background: linear-gradient(135deg, var(--primary), var(--primary-light));
  color: white;
  border-color: var(--primary);
  transform: translateX(-2px);
}

.sidebar-btn .btn-icon-emoji {
  font-size: 15px;
  width: 20px;
  text-align: center;
}

.sidebar-btn.accent {
  background: linear-gradient(135deg, var(--accent), var(--accent-dark));
  color: var(--primary-dark);
  font-weight: 700;
  border-color: var(--accent);
}

.sidebar-btn.accent:hover { filter: brightness(1.1); }

.sidebar-btn.danger:hover {
  background: linear-gradient(135deg, #f56565, #e53e3e);
  border-color: var(--danger);
  color: white;
}

/* ============ Tabs Bar ============ */
.tabs-bar {
  background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary) 100%);
  border-bottom: 2px solid var(--accent);
  display: flex;
  align-items: center;
  padding: 0 10px;
  min-height: var(--tabs-height);
  box-shadow: var(--shadow-sm);
  flex-shrink: 0;
  gap: 6px;
  position: relative;
}

[data-theme="dark"] .tabs-bar {
  background: linear-gradient(135deg, #0f1724 0%, #1a2a44 100%);
}

.tabs-controls {
  display: flex;
  gap: 4px;
  align-items: center;
  padding: 4px 8px;
  border-left: 1px solid rgba(255,255,255,0.15);
  margin-left: 6px;
}

.tab-control-btn {
  background: rgba(255,255,255,0.1);
  border: 1px solid rgba(255,255,255,0.15);
  color: white;
  width: 32px; height: 32px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.2s;
  padding: 0;
}

.tab-control-btn:hover:not(:disabled) {
  background: var(--accent);
  color: var(--primary-dark);
  border-color: var(--accent);
  transform: translateY(-1px);
}

.tab-control-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.tab-control-btn.active {
  background: var(--accent);
  color: var(--primary-dark);
}

.tabs-list {
  display: flex;
  gap: 3px;
  flex: 1;
  overflow-x: auto;
  padding: 4px 0;
}

.tabs-list::-webkit-scrollbar { height: 4px; }
.tabs-list::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.2); border-radius: 2px; }

.tab-item {
  padding: 7px 14px;
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.1);
  cursor: pointer;
  font-family: inherit;
  font-size: 12px;
  font-weight: 500;
  color: rgba(255,255,255,0.85);
  border-radius: 6px 6px 0 0;
  display: flex;
  align-items: center;
  gap: 8px;
  white-space: nowrap;
  transition: all 0.2s;
  position: relative;
  border-bottom: 2px solid transparent;
}

.tab-item:hover {
  background: rgba(255,255,255,0.15);
  color: white;
}

.tab-item.active {
  background: var(--accent);
  color: var(--primary-dark);
  font-weight: 700;
  border-bottom-color: var(--primary-dark);
}

.tab-close {
  width: 18px; height: 18px;
  border-radius: 50%;
  background: transparent;
  border: none;
  cursor: pointer;
  color: inherit;
  font-size: 11px;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0.6;
  transition: all 0.2s;
}

.tab-item:hover .tab-close { opacity: 1; }
.tab-close:hover { background: var(--danger); color: white; }

/* ============ Search/Filter Bar ============ */
.search-bar {
  background: var(--panel-bg);
  border-bottom: 1px solid var(--border);
  padding: 8px 16px;
  display: flex;
  gap: 10px;
  align-items: center;
  flex-shrink: 0;
  flex-wrap: wrap;
}

.search-input-wrapper {
  flex: 1;
  min-width: 200px;
  position: relative;
}

.search-input {
  width: 100%;
  padding: 7px 12px 7px 32px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: 13px;
  background: var(--card-bg);
  color: var(--text);
}

.search-input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(201,169,97,0.15);
}

.search-icon {
  position: absolute;
  right: 10px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-muted);
  font-size: 14px;
  pointer-events: none;
}

.filter-select {
  padding: 7px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: 12px;
  background: var(--card-bg);
  color: var(--text);
  min-width: 140px;
}

.filter-select:focus {
  outline: none;
  border-color: var(--accent);
}

.search-stats {
  font-size: 11px;
  color: var(--text-muted);
  padding: 4px 10px;
  background: var(--border-light);
  border-radius: 12px;
  white-space: nowrap;
}

/* ============ Stats Panel ============ */
.stats-panel {
  background: var(--panel-bg);
  border-bottom: 1px solid var(--border);
  padding: 10px 16px;
  display: flex;
  gap: 12px;
  align-items: center;
  flex-shrink: 0;
  flex-wrap: wrap;
}

.stat-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 12px;
  background: var(--border-light);
  border-radius: 16px;
  font-size: 12px;
  font-weight: 500;
  color: var(--text);
}

.stat-chip .stat-icon { font-size: 14px; }

.stat-chip .stat-value {
  font-weight: 700;
  color: var(--primary);
}

[data-theme="dark"] .stat-chip .stat-value {
  color: var(--accent);
}

.stat-chip.warning { background: #fef5e7; color: var(--warning); }
.stat-chip.danger { background: #fed7d7; color: var(--danger); }
.stat-chip.success { background: #c6f6d5; color: var(--success); }

[data-theme="dark"] .stat-chip.warning { background: rgba(221,107,32,0.2); }
[data-theme="dark"] .stat-chip.danger { background: rgba(229,62,62,0.2); }
[data-theme="dark"] .stat-chip.success { background: rgba(56,161,105,0.2); }

/* ============ Board Area ============ */
.board-area {
  flex: 1;
  overflow: auto;
  padding: 20px;
  background: var(--board-bg);
  transition: background 0.3s;
}

.board-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100%;
  color: var(--text-muted);
  text-align: center;
}

.board-empty-icon { font-size: 80px; opacity: 0.3; margin-bottom: 20px; }
.board-empty h2 { font-size: 20px; color: var(--primary); margin-bottom: 10px; }
[data-theme="dark"] .board-empty h2 { color: var(--accent); }
.board-empty p { margin-bottom: 20px; }

.board-wrapper {
  display: flex;
  gap: 15px;
  align-items: flex-start;
  min-height: calc(100vh - 280px);
  padding-bottom: 20px;
}

/* ============ Columns ============ */
.column {
  border-radius: var(--radius);
  min-width: 300px;
  max-width: 300px;
  max-height: calc(100vh - 280px);
  display: flex;
  flex-direction: column;
  box-shadow: var(--shadow-sm);
  border: 1px solid var(--border);
  transition: all 0.2s;
}

.column.filtered-out { opacity: 0.3; pointer-events: none; }

.column.drag-over {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(201,169,97,0.25);
}

.column-header {
  padding: 12px 14px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 1px solid var(--border);
  cursor: grab;
  background: linear-gradient(to bottom, rgba(255,255,255,0.3), transparent);
  border-radius: var(--radius) var(--radius) 0 0;
}

.column-header:active { cursor: grabbing; }
.column.dragging-col { opacity: 0.4; transform: rotate(1deg); }

.column-title {
  font-weight: 700;
  font-size: 14px;
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1;
  overflow: hidden;
}

.column-count {
  font-size: 11px;
  padding: 2px 8px;
  border-radius: 10px;
  font-weight: 700;
  background: rgba(0,0,0,0.15);
  color: inherit;
}

.column-actions { display: flex; gap: 4px; }

.column-action-btn {
  background: transparent;
  border: none;
  cursor: pointer;
  color: inherit;
  opacity: 0.7;
  padding: 4px 6px;
  border-radius: 4px;
  font-size: 14px;
  transition: all 0.2s;
}

.column-action-btn:hover {
  background: rgba(0,0,0,0.1);
  opacity: 1;
}

.column-body {
  padding: 10px;
  overflow-y: auto;
  flex: 1;
  min-height: 60px;
}

.column-body.drag-target { background: rgba(201,169,97,0.1); }
.column-footer { padding: 8px; border-top: 1px solid var(--border); }

.add-card-btn {
  width: 100%;
  background: transparent;
  border: 1px dashed var(--border);
  color: var(--text-muted);
  padding: 8px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-family: inherit;
  font-size: 13px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  transition: all 0.2s;
}

.add-card-btn:hover {
  background: var(--card-bg);
  border-color: var(--accent);
  color: var(--accent-dark);
}

.add-column-btn {
  background: rgba(255,255,255,0.5);
  border: 2px dashed var(--border);
  border-radius: var(--radius);
  min-width: 280px;
  min-height: 100px;
  cursor: pointer;
  color: var(--text-muted);
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  transition: all 0.2s;
}

[data-theme="dark"] .add-column-btn { background: rgba(255,255,255,0.05); }

.add-column-btn:hover {
  background: var(--card-bg);
  border-color: var(--accent);
  color: var(--accent-dark);
}

/* ============ Cards ============ */
.card {
  background: var(--card-bg);
  border-radius: 8px;
  padding: 12px;
  margin-bottom: 8px;
  box-shadow: var(--shadow-sm);
  cursor: grab;
  border: 1px solid var(--border);
  transition: all 0.15s;
  position: relative;
}

.card:hover {
  box-shadow: 0 4px 12px rgba(0,0,0,0.12);
  transform: translateY(-1px);
}

.card:active { cursor: grabbing; }
.card.dragging { opacity: 0.4; transform: rotate(2deg); }
.card.filtered-out { opacity: 0.15; pointer-events: none; }
.card.keyboard-focused { outline: 3px solid var(--accent); outline-offset: 2px; }

.card-tags { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 8px; }

.card-tag {
  font-size: 10px;
  padding: 2px 8px;
  border-radius: 10px;
  font-weight: 600;
  color: white;
}

.card-title {
  font-weight: 700;
  font-size: 13px;
  margin-bottom: 6px;
  line-height: 1.5;
}

.card-desc {
  font-size: 12px;
  margin-bottom: 8px;
  line-height: 1.6;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  opacity: 0.85;
}

.card-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  font-size: 11px;
  align-items: center;
  opacity: 0.9;
}

.card-meta-item {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  background: rgba(0,0,0,0.08);
  padding: 2px 6px;
  border-radius: 4px;
}

[data-theme="dark"] .card-meta-item {
  background: rgba(255,255,255,0.15);
  color: var(--text);
}

.card-meta-item.overdue { background: #fed7d7; color: var(--danger); font-weight: 600; opacity: 1; }
.card-meta-item.soon { background: #fef5e7; color: var(--warning); font-weight: 600; opacity: 1; }

[data-theme="dark"] .card-meta-item.overdue { background: rgba(229,62,62,0.3); color: #feb2b2; }
[data-theme="dark"] .card-meta-item.soon { background: rgba(221,107,32,0.3); color: #fbd38d; }

.card-assignees { display: flex; margin-right: auto; }

.assignee-avatar {
  width: 22px; height: 22px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--primary), var(--primary-light));
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 10px;
  font-weight: 700;
  margin-right: -6px;
  border: 2px solid var(--card-bg);
}

.card-checklist-progress {
  width: 100%;
  height: 4px;
  background: rgba(0,0,0,0.1);
  border-radius: 2px;
  overflow: hidden;
  margin-top: 8px;
}

[data-theme="dark"] .card-checklist-progress { background: rgba(255,255,255,0.15); }

.card-checklist-bar {
  height: 100%;
  background: linear-gradient(90deg, var(--success), #48bb78);
  transition: width 0.3s;
}

.card-dep-indicator {
  position: absolute;
  top: 6px;
  left: 6px;
  font-size: 10px;
  background: var(--info);
  color: white;
  padding: 1px 6px;
  border-radius: 8px;
  font-weight: 600;
}

/* ============ Modals ============ */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(26,54,93,0.6);
  backdrop-filter: blur(3px);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  padding: 20px;
  animation: fadeIn 0.2s;
}

.modal-overlay.active { display: flex; }

@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }

.modal {
  background: var(--panel-bg);
  color: var(--text);
  border-radius: 12px;
  width: 100%;
  max-width: 600px;
  max-height: 90vh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  box-shadow: var(--shadow-lg);
  animation: slideUp 0.25s;
}

.modal-lg { max-width: 800px; }

@keyframes slideUp {
  from { opacity: 0; transform: translateY(20px); }
  to { opacity: 1; transform: translateY(0); }
}

.modal-header {
  background: linear-gradient(135deg, var(--primary), var(--primary-light));
  color: white;
  padding: 16px 20px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 3px solid var(--accent);
}

.modal-title {
  font-size: 16px;
  font-weight: 700;
  display: flex;
  align-items: center;
  gap: 10px;
}

.modal-close {
  background: rgba(255,255,255,0.15);
  border: none;
  color: white;
  width: 30px; height: 30px;
  border-radius: 50%;
  cursor: pointer;
  font-size: 16px;
  transition: all 0.2s;
}

.modal-close:hover { background: var(--danger); }

.modal-body {
  padding: 20px;
  overflow-y: auto;
  flex: 1;
}

.modal-footer {
  padding: 14px 20px;
  border-top: 1px solid var(--border);
  display: flex;
  justify-content: flex-start;
  gap: 8px;
  background: var(--border-light);
}

/* ============ Forms ============ */
.form-group { margin-bottom: 16px; }

.form-label {
  display: block;
  font-size: 13px;
  font-weight: 600;
  color: var(--primary);
  margin-bottom: 6px;
}

[data-theme="dark"] .form-label { color: var(--accent-light); }

.form-label .required { color: var(--danger); }

.form-input, .form-textarea, .form-select {
  width: 100%;
  padding: 9px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: 13px;
  background: var(--card-bg);
  color: var(--text);
  transition: all 0.2s;
}

.form-input:focus, .form-textarea:focus, .form-select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(201,169,97,0.15);
}

.form-textarea { resize: vertical; min-height: 70px; line-height: 1.6; }

.form-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

.form-hint {
  font-size: 11px;
  color: var(--text-muted);
  margin-top: 4px;
}

.form-error {
  font-size: 11px;
  color: var(--danger);
  margin-top: 4px;
}

/* ============ Info Box ============ */
.info-box {
  background: linear-gradient(to left, #ebf8ff, #e6f6ff);
  border: 1px solid #90cdf4;
  border-right: 4px solid var(--primary-light);
  padding: 18px 22px;
  border-radius: 8px;
  margin: 20px 0;
  font-size: 12px;
  color: var(--text);
}

[data-theme="dark"] .info-box {
  background: rgba(49, 130, 206, 0.15);
  border-color: rgba(49, 130, 206, 0.3);
  border-right-color: var(--info);
  color: var(--text);
}

.info-box strong { color: var(--primary); }
[data-theme="dark"] .info-box strong { color: var(--accent-light); }

/* ============ Webhook Section ============ */
.webhook-section {
  background: var(--border-light);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 14px;
  margin-top: 12px;
}

.webhook-section-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
}

.webhook-section-title {
  font-size: 13px;
  font-weight: 700;
  color: var(--primary);
  display: flex;
  align-items: center;
  gap: 6px;
}

[data-theme="dark"] .webhook-section-title { color: var(--accent-light); }

.webhook-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  color: var(--text-muted);
}

.switch {
  position: relative;
  display: inline-block;
  width: 36px;
  height: 20px;
}

.switch input { opacity: 0; width: 0; height: 0; }

.slider {
  position: absolute;
  cursor: pointer;
  inset: 0;
  background: var(--border);
  transition: 0.3s;
  border-radius: 20px;
}

.slider::before {
  position: absolute;
  content: "";
  height: 14px;
  width: 14px;
  right: 3px;
  bottom: 3px;
  background: white;
  transition: 0.3s;
  border-radius: 50%;
}

input:checked + .slider { background: var(--success); }
input:checked + .slider::before { transform: translateX(-16px); }

.webhook-url-row {
  display: flex;
  gap: 6px;
  margin-top: 8px;
}

.webhook-url-row .form-input {
  flex: 1;
  font-family: monospace;
  font-size: 12px;
  direction: ltr;
  text-align: left;
}

.webhook-status {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 10px;
  padding: 8px 10px;
  background: var(--card-bg);
  border-radius: 4px;
  font-size: 11px;
  color: var(--text-muted);
}

.webhook-status-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--text-muted);
  flex-shrink: 0;
}

.webhook-status-dot.success { background: var(--success); }
.webhook-status-dot.error { background: var(--danger); }
.webhook-status-dot.pending { background: var(--warning); animation: pulse 1.5s infinite; }

/* ============ Webhook Indicator ============ */
.webhook-indicator {
  position: fixed;
  bottom: 20px;
  right: 20px;
  background: var(--panel-bg);
  border: 1px solid var(--border);
  border-radius: 20px;
  padding: 6px 12px;
  font-size: 11px;
  display: flex;
  align-items: center;
  gap: 6px;
  box-shadow: var(--shadow);
  z-index: 1500;
  opacity: 0;
  transition: opacity 0.3s;
  pointer-events: none;
}

.webhook-indicator.visible { opacity: 1; }

.webhook-indicator.success { border-color: var(--success); }
.webhook-indicator.error { border-color: var(--danger); }

/* ============ Checklist ============ */
.checklist-items {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 8px;
  background: var(--border-light);
  max-height: 200px;
  overflow-y: auto;
}

.checklist-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px;
  border-radius: 4px;
  transition: background 0.15s;
}

.checklist-item:hover { background: var(--card-bg); }

.checklist-item input[type="checkbox"] {
  width: 16px; height: 16px;
  accent-color: var(--success);
  cursor: pointer;
}

.checklist-item.done .checklist-text {
  text-decoration: line-through;
  color: var(--text-muted);
}

.checklist-text { flex: 1; font-size: 13px; }

.checklist-remove {
  background: transparent;
  border: none;
  color: var(--danger);
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.2s;
}

.checklist-item:hover .checklist-remove { opacity: 1; }

.add-checklist-row {
  display: flex;
  gap: 6px;
  margin-top: 8px;
}

/* ============ Tags ============ */
.tags-container {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--border-light);
  min-height: 50px;
}

.tag-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 10px;
  border-radius: 12px;
  font-size: 12px;
  font-weight: 600;
  color: white;
  cursor: pointer;
  transition: all 0.15s;
  border: 2px solid transparent;
}

.tag-chip.selected {
  border-color: var(--primary);
  transform: scale(1.05);
  box-shadow: 0 0 0 2px var(--card-bg), 0 0 0 4px var(--primary);
}

.tag-chip:hover { opacity: 0.85; }

.tag-manage-list {
  max-height: 300px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

.tag-manage-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
}

.tag-manage-item:last-child { border-bottom: none; }

.tag-color-preview {
  width: 30px; height: 30px;
  border-radius: 6px;
  border: 1px solid rgba(0,0,0,0.1);
}

.tag-manage-name { flex: 1; font-weight: 600; }

/* ============ History ============ */
.history-list {
  max-height: 300px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--border-light);
}

.history-item {
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
  font-size: 12px;
  display: flex;
  gap: 10px;
  align-items: flex-start;
}

.history-item:last-child { border-bottom: none; }

.history-icon {
  width: 24px; height: 24px;
  border-radius: 50%;
  background: var(--primary);
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  flex-shrink: 0;
}

.history-content { flex: 1; }
.history-date { font-size: 10px; color: var(--text-muted); margin-top: 2px; }

/* ============ Assignees ============ */
.assignees-container {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 8px;
  background: var(--border-light);
}

.assignee-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  background: var(--card-bg);
  border-radius: 4px;
  margin-bottom: 4px;
}

.assignee-item:last-child { margin-bottom: 0; }

.assignee-avatar-lg {
  width: 28px; height: 28px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--primary), var(--primary-light));
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  font-weight: 700;
}

.assignee-name { flex: 1; font-size: 13px; }

/* ============ Dependencies ============ */
.dep-list {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 8px;
  background: var(--border-light);
  max-height: 180px;
  overflow-y: auto;
}

.dep-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  background: var(--card-bg);
  border-radius: 4px;
  margin-bottom: 4px;
  font-size: 12px;
}

.dep-item:last-child { margin-bottom: 0; }

.dep-status {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--text-muted);
}

.dep-title { flex: 1; font-weight: 500; }

/* ============ Color Picker ============ */
.color-picker-row {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}

.color-swatch {
  width: 30px; height: 30px;
  border-radius: 6px;
  cursor: pointer;
  border: 2px solid transparent;
  transition: all 0.15s;
}

.color-swatch:hover { transform: scale(1.1); }
.color-swatch.selected {
  border-color: var(--primary);
  transform: scale(1.15);
  box-shadow: 0 0 0 2px var(--card-bg), 0 0 0 4px var(--primary);
}

.color-picker-custom {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
}

.color-picker-custom input[type="color"] {
  width: 40px;
  height: 34px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  cursor: pointer;
  background: transparent;
}

/* ============ Toast ============ */
.toast-container {
  position: fixed;
  bottom: 20px;
  left: 20px;
  z-index: 2000;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.toast {
  background: var(--primary);
  color: white;
  padding: 12px 18px;
  border-radius: 8px;
  box-shadow: var(--shadow-lg);
  font-size: 13px;
  display: flex;
  align-items: center;
  gap: 10px;
  animation: slideInLeft 0.3s;
  min-width: 250px;
}

.toast.success { background: var(--success); }
.toast.error { background: var(--danger); }
.toast.warning { background: var(--warning); }

@keyframes slideInLeft {
  from { opacity: 0; transform: translateX(-30px); }
  to { opacity: 1; transform: translateX(0); }
}

/* ============ Auto-save Indicator ============ */
.autosave-indicator {
  position: fixed;
  bottom: 50px;
  left: 20px;
  background: var(--panel-bg);
  color: var(--text);
  padding: 6px 12px;
  border-radius: 20px;
  font-size: 11px;
  box-shadow: var(--shadow);
  border: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: 6px;
  opacity: 0;
  transition: opacity 0.3s;
  z-index: 1500;
}

.autosave-indicator.visible { opacity: 1; }

.autosave-dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--success);
  animation: pulse 2s infinite;
}

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.4; }
}

/* ============ Scrollbar ============ */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }

/* ============ Empty States ============ */
.empty-state {
  text-align: center;
  padding: 30px 20px;
  color: var(--text-muted);
  font-size: 13px;
}

.empty-state-icon {
  font-size: 40px;
  opacity: 0.3;
  margin-bottom: 10px;
}

/* ============ Footer ============ */
.app-footer {
  background: linear-gradient(135deg, var(--primary), var(--primary-light));
  color: white;
  padding: 8px 20px;
  font-size: 11px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-top: 2px solid var(--accent);
  flex-shrink: 0;
}

.footer-brand { color: var(--accent); font-weight: 700; }

/* ============ Keyboard Hints ============ */
.kbd {
  display: inline-block;
  padding: 2px 6px;
  background: var(--border-light);
  border: 1px solid var(--border);
  border-radius: 4px;
  font-family: monospace;
  font-size: 11px;
  color: var(--text);
  box-shadow: 0 1px 0 var(--border);
}

/* ============ Responsive ============ */
@media (max-width: 768px) {
  .sidebar { position: absolute; right: 0; top: 0; bottom: 0; z-index: 200; }
  .sidebar.collapsed { width: 0; }
}
</style>
</head>
<body>

<!-- ========== Header ========== -->
<header class="app-header" role="banner">
  <div class="app-brand">
    <div class="app-logo" aria-hidden="true"></div>
    <div class="app-brand-text">
      <div class="app-title">🎯 ابزار مدیریت بردهای کانبان</div>
      <div class="app-subtitle">مدیریت بردهای کانبان با قابلیت ذخیره و بازیابی</div>
    </div>
  </div>
  <div class="header-actions">
    <div style="font-size:11px;opacity:0.85;">طراح: <strong style="color:var(--accent);">هادی خزاعی اصل</strong></div>
  </div>
</header>

<!-- ========== Tabs Bar ========== -->
<div class="tabs-bar" role="navigation" aria-label="مدیریت بردها">
  <div class="tabs-controls">
    <button class="tab-control-btn" onclick="toggleSidebar()" title="بستن/باز کردن نوار ابزار" id="sidebarToggleBtn" aria-label="تغییر وضعیت نوار ابزار">✕</button>
    <button class="tab-control-btn" onclick="CommandHistory.undo()" title="بازگشت (Ctrl+Z)" id="undoBtn" aria-label="بازگشت" disabled>↶</button>
    <button class="tab-control-btn" onclick="CommandHistory.redo()" title="از نو (Ctrl+Y)" id="redoBtn" aria-label="از نو" disabled>↷</button>
    <button class="tab-control-btn" onclick="toggleTheme()" title="تغییر تم روشن/تاریک" id="themeToggleBtn" aria-label="تغییر تم">🌙</button>
    <button class="tab-control-btn" onclick="openNewBoardModal()" title="افزودن برد جدید" style="font-size:16px;font-weight:700;" aria-label="افزودن برد جدید">➕</button>
  </div>
  <div class="tabs-list" id="tabsList" role="tablist"></div>
</div>

<!-- ========== Main Layout ========== -->
<div class="app-main">
  <!-- ========== Sidebar ========== -->
  <aside class="sidebar" id="sidebar" role="complementary" aria-label="نوار ابزار">
    <div class="sidebar-header">
      <div class="sidebar-title">🛠️ ابزارها</div>
    </div>
    <div class="sidebar-body">

      <div class="sidebar-group" data-group="project">
        <div class="sidebar-group-header" onclick="toggleGroup('project')" role="button" aria-expanded="true">
          <div class="group-icon">📁 پروژه</div>
          <span class="group-arrow">▼</span>
        </div>
        <div class="sidebar-group-body">
          <button class="sidebar-btn" onclick="openProjectSettings()">
            <span class="btn-icon-emoji">📋</span>
            <span>تنظیمات پروژه</span>
          </button>
          <button class="sidebar-btn" onclick="saveProject()">
            <span class="btn-icon-emoji">💾</span>
            <span>ذخیره پروژه</span>
          </button>
          <button class="sidebar-btn" onclick="loadProject()">
            <span class="btn-icon-emoji">📂</span>
            <span>بارگذاری</span>
          </button>
          <button class="sidebar-btn" onclick="openExportModal()">
            <span class="btn-icon-emoji">📤</span>
            <span>خروجی (MD/CSV)</span>
          </button>
        </div>
      </div>

      <div class="sidebar-group" data-group="board">
        <div class="sidebar-group-header" onclick="toggleGroup('board')" role="button" aria-expanded="true">
          <div class="group-icon">📊 مدیریت برد</div>
          <span class="group-arrow">▼</span>
        </div>
        <div class="sidebar-group-body">
          <button class="sidebar-btn accent" onclick="openNewBoardModal()">
            <span class="btn-icon-emoji">➕</span>
            <span>ایجاد برد جدید</span>
          </button>
          <button class="sidebar-btn" onclick="editCurrentBoard()">
            <span class="btn-icon-emoji">✏️</span>
            <span>ویرایش برد فعلی</span>
          </button>
          <button class="sidebar-btn danger" onclick="deleteCurrentBoard()">
            <span class="btn-icon-emoji">🗑️</span>
            <span>حذف برد فعلی</span>
          </button>
        </div>
      </div>

      <div class="sidebar-group" data-group="column">
        <div class="sidebar-group-header" onclick="toggleGroup('column')" role="button" aria-expanded="true">
          <div class="group-icon">📑 ستون‌ها</div>
          <span class="group-arrow">▼</span>
        </div>
        <div class="sidebar-group-body">
          <button class="sidebar-btn" onclick="openNewColumnModal()">
            <span class="btn-icon-emoji">➕</span>
            <span>افزودن ستون جدید</span>
          </button>
          <div class="form-hint" style="padding:6px;font-size:11px;">
            💡 برای تغییر ترتیب ستون‌ها، هدر آن‌ها را دراگ کنید
          </div>
        </div>
      </div>

      <div class="sidebar-group" data-group="tags">
        <div class="sidebar-group-header" onclick="toggleGroup('tags')" role="button" aria-expanded="true">
          <div class="group-icon">🏷️ برچسب‌ها</div>
          <span class="group-arrow">▼</span>
        </div>
        <div class="sidebar-group-body">
          <button class="sidebar-btn" onclick="openTagManager()">
            <span class="btn-icon-emoji">🏷️</span>
            <span>مدیریت تگ‌های عمومی</span>
          </button>
        </div>
      </div>

      <div class="sidebar-group" data-group="view">
        <div class="sidebar-group-header" onclick="toggleGroup('view')" role="button" aria-expanded="true">
          <div class="group-icon">🎨 نمایش</div>
          <span class="group-arrow">▼</span>
        </div>
        <div class="sidebar-group-body">
          <button class="sidebar-btn" onclick="toggleTheme()">
            <span class="btn-icon-emoji" id="themeIcon">🌙</span>
            <span id="themeText">حالت روشن</span>
          </button>
          <button class="sidebar-btn" onclick="toggleSidebar()">
            <span class="btn-icon-emoji">✕</span>
            <span>بستن نوار ابزار</span>
          </button>
        </div>
      </div>

      <div class="sidebar-group" data-group="help">
        <div class="sidebar-group-header" onclick="toggleGroup('help')" role="button" aria-expanded="true">
          <div class="group-icon">⌨️ میانبرها</div>
          <span class="group-arrow">▼</span>
        </div>
        <div class="sidebar-group-body">
          <div style="padding:8px;font-size:11px;color:var(--text-muted);line-height:2;">
            <div><span class="kbd">Ctrl+S</span> ذخیره سریع</div>
            <div><span class="kbd">Ctrl+Z</span> بازگشت</div>
            <div><span class="kbd">Ctrl+Y</span> از نو</div>
            <div><span class="kbd">Ctrl+F</span> جستجو</div>
            <div><span class="kbd">Esc</span> بستن پنجره‌ها</div>
            <div><span class="kbd">Tab</span> انتخاب کارت</div>
            <div><span class="kbd">Alt+→/←</span> جابجایی کارت</div>
            <div><span class="kbd">Enter</span> ویرایش کارت</div>
          </div>
        </div>
      </div>

    </div>
  </aside>

  <!-- Board Container -->
  <div style="flex:1;display:flex;flex-direction:column;overflow:hidden;">
    <!-- Search/Filter Bar -->
    <div class="search-bar" id="searchBar" role="search" aria-label="جستجو و فیلتر">
      <div class="search-input-wrapper">
        <input type="text" class="search-input" id="searchInput" placeholder="جستجو در عنوان و توضیحات کارت‌ها..." aria-label="جستجو">
        <span class="search-icon">🔍</span>
      </div>
      <select class="filter-select" id="filterTag" aria-label="فیلتر بر اساس تگ">
        <option value="">🏷️ همه تگ‌ها</option>
      </select>
      <select class="filter-select" id="filterAssignee" aria-label="فیلتر بر اساس مسئول">
        <option value="">👥 همه مسئولین</option>
      </select>
      <select class="filter-select" id="filterStatus" aria-label="فیلتر بر اساس وضعیت">
        <option value="">📊 همه وضعیت‌ها</option>
        <option value="overdue">⚠️ عقب‌افتاده</option>
        <option value="soon">⏰ نزدیک به موعد</option>
        <option value="normal">✅ عادی</option>
        <option value="no-date">📅 بدون تاریخ</option>
      </select>
      <div class="search-stats" id="searchStats">۰ کارت</div>
    </div>

    <!-- Stats Panel -->
    <div class="stats-panel" id="statsPanel" role="status" aria-label="آمار برد">
      <div class="stat-chip">
        <span class="stat-icon">📋</span>
        <span>کل کارت‌ها:</span>
        <span class="stat-value" id="statTotal">0</span>
      </div>
      <div class="stat-chip success">
        <span class="stat-icon">✅</span>
        <span>تکمیل‌شده:</span>
        <span class="stat-value" id="statDone">0</span>
      </div>
      <div class="stat-chip warning">
        <span class="stat-icon">⏰</span>
        <span>نزدیک به موعد:</span>
        <span class="stat-value" id="statSoon">0</span>
      </div>
      <div class="stat-chip danger">
        <span class="stat-icon">⚠️</span>
        <span>عقب‌افتاده:</span>
        <span class="stat-value" id="statOverdue">0</span>
      </div>
      <div class="stat-chip">
        <span class="stat-icon">📊</span>
        <span>پیشرفت:</span>
        <span class="stat-value" id="statProgress">0%</span>
      </div>
    </div>

    <!-- Board Area -->
    <div class="board-area" id="boardArea" role="main">
      <div class="board-empty" id="boardEmpty">
        <div class="board-empty-icon">📋</div>
        <h2>هیچ بردی وجود ندارد</h2>
        <p>برای شروع، یک برد جدید ایجاد کنید</p>
        <button class="btn btn-accent" onclick="openNewBoardModal()">➕ ایجاد اولین برد</button>
      </div>
      <div class="board-wrapper" id="boardWrapper" style="display:none;"></div>
    </div>
  </div>
</div>

<!-- ========== Footer ========== -->
<footer class="app-footer" role="contentinfo">
  <div>طراحی و توسعه: <span class="footer-brand">هادی خزاعی اصل</span> | معمار و توسعه‌دهنده ارشد نرم‌افزار</div>
  <div>نسخه ۱.۰.۰ | <span id="currentDate"></span></div>
</footer>

<!-- ========== Auto-save Indicator ========== -->
<div class="autosave-indicator" id="autosaveIndicator" role="status" aria-live="polite">
  <div class="autosave-dot"></div>
  <span id="autosaveText">ذخیره خودکار فعال</span>
</div>

<!-- ========== Webhook Indicator ========== -->
<div class="webhook-indicator" id="webhookIndicator" role="status" aria-live="polite">
  <div class="webhook-status-dot" id="webhookIndicatorDot"></div>
  <span id="webhookIndicatorText">در حال ارسال...</span>
</div>

<!-- ========== Modal: New/Edit Board ========== -->
<div class="modal-overlay" id="boardModal" role="dialog" aria-modal="true" aria-labelledby="boardModalTitle">
  <div class="modal">
    <div class="modal-header">
      <div class="modal-title">📋 <span id="boardModalTitle">ایجاد برد جدید</span></div>
      <button class="modal-close" onclick="closeModal('boardModal')" aria-label="بستن">✕</button>
    </div>
    <div class="modal-body">
      <div class="form-group">
        <label class="form-label">عنوان برد <span class="required">*</span></label>
        <input type="text" class="form-input" id="boardTitle" placeholder="مثلاً: اسپرینت فعلی">
        <div class="form-error" id="boardTitleError" style="display:none;"></div>
      </div>
      <div class="form-group">
        <label class="form-label">توضیحات</label>
        <textarea class="form-textarea" id="boardDesc" placeholder="توضیحات مربوط به برد..."></textarea>
      </div>
      <div class="form-group">
        <label class="form-label">🎨 رنگ پس‌زمینه برد</label>
        <div class="color-picker-row" id="boardBgColors"></div>
        <div class="color-picker-custom">
          <input type="color" id="boardBgCustom" value="#e2e8f0" onchange="selectCustomColor('board', 'bg', this.value)">
          <span style="font-size:12px;color:var(--text-muted);">انتخاب رنگ دلخواه</span>
        </div>
      </div>
    </div>
    <div class="modal-footer">
      <button class="btn btn-success" onclick="saveBoard()">💾 ذخیره</button>
      <button class="btn btn-ghost" onclick="closeModal('boardModal')">❌ انصراف</button>
    </div>
  </div>
</div>

<!-- ========== Modal: Column ========== -->
<div class="modal-overlay" id="columnModal" role="dialog" aria-modal="true" aria-labelledby="columnModalTitle">
  <div class="modal" style="max-width:500px;">
    <div class="modal-header">
      <div class="modal-title">📊 <span id="columnModalTitle">ایجاد ستون جدید</span></div>
      <button class="modal-close" onclick="closeModal('columnModal')" aria-label="بستن">✕</button>
    </div>
    <div class="modal-body">
      <div class="form-group">
        <label class="form-label">عنوان ستون <span class="required">*</span></label>
        <input type="text" class="form-input" id="columnTitle" placeholder="مثلاً: در حال انجام">
        <div class="form-error" id="columnTitleError" style="display:none;"></div>
      </div>
      <div class="form-group">
        <label class="form-label">🎨 رنگ پس‌زمینه ستون</label>
        <div class="color-picker-row" id="columnBgColors"></div>
        <div class="color-picker-custom">
          <input type="color" id="columnBgCustom" value="#edf2f7" onchange="selectCustomColor('column', 'bg', this.value)">
          <span style="font-size:12px;color:var(--text-muted);">انتخاب رنگ دلخواه</span>
        </div>
      </div>
      <div class="form-group">
        <label class="form-label">🖋️ رنگ متن ستون</label>
        <div class="color-picker-row" id="columnTextColors"></div>
        <div class="color-picker-custom">
          <input type="color" id="columnTextCustom" value="#1a202c" onchange="selectCustomColor('column', 'text', this.value)">
          <span style="font-size:12px;color:var(--text-muted);">انتخاب رنگ دلخواه</span>
        </div>
      </div>
    </div>
    <div class="modal-footer">
      <button class="btn btn-success" onclick="saveColumn()">💾 ذخیره</button>
      <button class="btn btn-ghost" onclick="closeModal('columnModal')">❌ انصراف</button>
    </div>
  </div>
</div>

<!-- ========== Modal: Card ========== -->
<div class="modal-overlay" id="cardModal" role="dialog" aria-modal="true" aria-labelledby="cardModalTitle">
  <div class="modal modal-lg">
    <div class="modal-header">
      <div class="modal-title">📝 <span id="cardModalTitle">ایجاد کارت جدید</span></div>
      <button class="modal-close" onclick="closeModal('cardModal')" aria-label="بستن">✕</button>
    </div>
    <div class="modal-body">
      <div class="form-group">
        <label class="form-label">عنوان کارت <span class="required">*</span></label>
        <input type="text" class="form-input" id="cardTitle" placeholder="عنوان وظیفه...">
      </div>
      <div class="form-group">
        <label class="form-label">توضیحات <span class="required">*</span></label>
        <textarea class="form-textarea" id="cardDesc" placeholder="توضیحات کامل وظیفه..."></textarea>
      </div>
      <div class="form-row">
        <div class="form-group">
          <label class="form-label">📅 تاریخ و زمان تخمینی انجام</label>
          <input type="datetime-local" class="form-input" id="cardDate">
          <div class="form-hint">تاریخ و ساعت تقریبی پایان وظیفه</div>
        </div>
        <div class="form-group">
          <label class="form-label">🎨 رنگ پس‌زمینه کارت</label>
          <div class="color-picker-row" id="cardBgColors"></div>
          <div class="color-picker-custom">
            <input type="color" id="cardBgCustom" value="#ffffff" onchange="selectCustomColor('card', 'bg', this.value)">
          </div>
        </div>
      </div>

      <div class="form-group">
        <label class="form-label">👥 افراد مسئول</label>
        <div class="assignees-container" id="assigneesContainer">
          <div class="empty-state" style="padding:10px;">
            <div style="font-size:12px;">هنوز کسی اضافه نشده</div>
          </div>
        </div>
        <div class="add-checklist-row" style="margin-top:6px;">
          <input type="text" class="form-input" id="newAssigneeInput" placeholder="نام یا نام کاربری...">
          <button class="btn btn-sm" onclick="addAssignee()">➕</button>
        </div>
      </div>

      <div class="form-group">
        <label class="form-label">🏷️ برچسب‌ها</label>
        <div class="tags-container" id="cardTagsContainer">
          <div class="empty-state" style="padding:10px;width:100%;">
            <div style="font-size:12px;">تگی وجود ندارد. از منوی اصلی تگ ایجاد کنید.</div>
          </div>
        </div>
      </div>

      <div class="form-group">
        <label class="form-label">🔗 وابستگی‌ها (Dependencies)</label>
        <div class="dep-list" id="depList">
          <div class="empty-state" style="padding:10px;">
            <div style="font-size:12px;">وابستگی انتخاب نشده</div>
          </div>
        </div>
        <div class="add-checklist-row" style="margin-top:6px;">
          <select class="form-select" id="depSelect"></select>
          <button class="btn btn-sm" onclick="addDependency()">➕</button>
        </div>
      </div>

      <div class="form-group">
        <label class="form-label">✅ چک‌لیست ریزوظایف</label>
        <div class="checklist-items" id="checklistContainer">
          <div class="empty-state" style="padding:10px;">
            <div style="font-size:12px;">آیتمی وجود ندارد</div>
          </div>
        </div>
        <div class="add-checklist-row" style="margin-top:6px;">
          <input type="text" class="form-input" id="newChecklistInput" placeholder="ریزوظیفه جدید...">
          <button class="btn btn-sm" onclick="addChecklistItem()">➕</button>
        </div>
      </div>

      <div class="form-group">
        <label class="form-label">📜 تاریخچه جابجایی</label>
        <div class="history-list" id="historyList">
          <div class="empty-state" style="padding:10px;">
            <div style="font-size:12px;">هنوز تاریخچه‌ای ثبت نشده</div>
          </div>
        </div>
      </div>
    </div>
    <div class="modal-footer">
      <button class="btn btn-success" onclick="saveCard()">💾 ذخیره</button>
      <button class="btn btn-danger" id="deleteCardBtn" onclick="deleteCard()" style="display:none;">🗑️ حذف</button>
      <button class="btn btn-ghost" onclick="closeModal('cardModal')">❌ انصراف</button>
    </div>
  </div>
</div>

<!-- ========== Modal: Project Settings ========== -->
<div class="modal-overlay" id="projectModal" role="dialog" aria-modal="true">
  <div class="modal" style="max-width:600px;">
    <div class="modal-header">
      <div class="modal-title">⚙️ تنظیمات پروژه</div>
      <button class="modal-close" onclick="closeModal('projectModal')" aria-label="بستن">✕</button>
    </div>
    <div class="modal-body">
      <div class="form-group">
        <label class="form-label">عنوان پروژه <span class="required">*</span></label>
        <input type="text" class="form-input" id="projectTitle">
      </div>
      <div class="form-group">
        <label class="form-label">توضیحات</label>
        <textarea class="form-textarea" id="projectDesc"></textarea>
      </div>
      <div class="form-group">
        <label class="form-label">تاریخ ایجاد</label>
        <input type="text" class="form-input" id="projectCreatedAt" disabled>
      </div>

      <!-- Webhook Configuration -->
      <div class="webhook-section">
        <div class="webhook-section-header">
          <div class="webhook-section-title">
            <span>🔗</span>
            <span>وب‌هوک (Webhook)</span>
          </div>
          <div class="webhook-toggle">
            <span>فعال</span>
            <label class="switch">
              <input type="checkbox" id="webhookEnabled" checked>
              <span class="slider"></span>
            </label>
          </div>
        </div>
        <div class="form-hint" style="margin-bottom:8px;">
          آدرس URL که رویدادهای پروژه به آن ارسال می‌شوند. در صورت خالی بودن، وب‌هوک غیرفعال است.
        </div>
        <div class="webhook-url-row">
          <input type="url" class="form-input" id="webhookUrl" placeholder="https://example.com/webhook" dir="ltr">
          <button class="btn btn-info btn-sm" onclick="testWebhook()" id="testWebhookBtn">🧪 تست</button>
        </div>
        <div class="webhook-status" id="webhookStatus" style="display:none;">
          <div class="webhook-status-dot" id="webhookStatusDot"></div>
          <div id="webhookStatusText" style="flex:1;"></div>
          <div id="webhookStatusTime" style="font-size:10px;color:var(--text-muted);"></div>
        </div>
        <div class="info-box" style="margin-top:12px;margin-bottom:0;font-size:11px;">
          <strong>📡 ساختار پیام:</strong> تمام عملیات‌ها با ساختار استاندارد JSON شامل eventId، timestamp، eventType، project، board و data ارسال می‌شوند.
        </div>
      </div>
    </div>
    <div class="modal-footer">
      <button class="btn btn-success" onclick="saveProjectSettings()">💾 ذخیره</button>
      <button class="btn btn-ghost" onclick="closeModal('projectModal')">❌ انصراف</button>
    </div>
  </div>
</div>

<!-- ========== Modal: Tag Manager ========== -->
<div class="modal-overlay" id="tagModal" role="dialog" aria-modal="true">
  <div class="modal">
    <div class="modal-header">
      <div class="modal-title">🏷️ مدیریت تگ‌های عمومی</div>
      <button class="modal-close" onclick="closeModal('tagModal')" aria-label="بستن">✕</button>
    </div>
    <div class="modal-body">
      <div class="form-row">
        <div class="form-group">
          <label class="form-label">نام تگ</label>
          <input type="text" class="form-input" id="newTagName" placeholder="مثلاً: فوری">
        </div>
        <div class="form-group">
          <label class="form-label">رنگ</label>
          <input type="color" class="form-input" id="newTagColor" value="#c9a961" style="height:38px;">
        </div>
      </div>
      <button class="btn btn-success btn-sm" onclick="addTag()">➕ افزودن تگ</button>
      <div style="margin-top:20px;">
        <label class="form-label">تگ‌های موجود</label>
        <div class="tag-manage-list" id="tagManageList"></div>
      </div>
    </div>
    <div class="modal-footer">
      <button class="btn btn-ghost" onclick="closeModal('tagModal')">بستن</button>
    </div>
  </div>
</div>

<!-- ========== Modal: Save ========== -->
<div class="modal-overlay" id="saveModal" role="dialog" aria-modal="true">
  <div class="modal" style="max-width:450px;">
    <div class="modal-header">
      <div class="modal-title">💾 ذخیره پروژه</div>
      <button class="modal-close" onclick="closeModal('saveModal')" aria-label="بستن">✕</button>
    </div>
    <div class="modal-body">
      <div class="form-group">
        <label class="form-label">نام فایل</label>
        <input type="text" class="form-input" id="saveFileName" value="xkanban-project.json">
      </div>
      <div class="info-box">
        <strong>ℹ️ توجه:</strong> فایل JSON در پوشه دانلودهای مرورگر ذخیره می‌شود.
      </div>
    </div>
    <div class="modal-footer">
      <button class="btn btn-success" onclick="downloadProject()">💾 دانلود فایل</button>
      <button class="btn btn-ghost" onclick="closeModal('saveModal')">انصراف</button>
    </div>
  </div>
</div>

<!-- ========== Modal: Export ========== -->
<div class="modal-overlay" id="exportModal" role="dialog" aria-modal="true">
  <div class="modal" style="max-width:500px;">
    <div class="modal-header">
      <div class="modal-title">📤 خروجی گرفتن از پروژه</div>
      <button class="modal-close" onclick="closeModal('exportModal')" aria-label="بستن">✕</button>
    </div>
    <div class="modal-body">
      <div class="form-group">
        <label class="form-label">انتخاب برد</label>
        <select class="form-select" id="exportBoardSelect">
          <option value="all">همه بردها</option>
        </select>
      </div>
      <div class="form-group">
        <label class="form-label">فرمت خروجی</label>
        <select class="form-select" id="exportFormat">
          <option value="markdown">📝 Markdown (MD)</option>
          <option value="csv">📊 CSV</option>
        </select>
      </div>
      <div class="info-box">
        <strong>ℹ️ توجه:</strong> فایل در پوشه دانلودهای مرورگر ذخیره می‌شود.
      </div>
    </div>
    <div class="modal-footer">
      <button class="btn btn-success" onclick="exportProject()">📤 خروجی</button>
      <button class="btn btn-ghost" onclick="closeModal('exportModal')">انصراف</button>
    </div>
  </div>
</div>

<!-- ========== Modal: Autosave Recovery ========== -->
<div class="modal-overlay" id="autosaveModal" role="dialog" aria-modal="true">
  <div class="modal" style="max-width:450px;">
    <div class="modal-header">
      <div class="modal-title">💾 بازیابی خودکار</div>
      <button class="modal-close" onclick="closeModal('autosaveModal')" aria-label="بستن">✕</button>
    </div>
    <div class="modal-body">
      <div class="info-box">
        <strong>⚠️ داده ذخیره‌شده خودکار یافت شد</strong><br>
        به نظر می‌رسد در جلسه قبلی، تغییراتی ذخیره شده ولی به فایل خروجی منتقل نشده‌اند.
        <div style="margin-top:10px;font-size:12px;color:var(--text-muted);">
          تاریخ ذخیره: <span id="autosaveDate"></span>
        </div>
      </div>
    </div>
    <div class="modal-footer">
      <button class="btn btn-success" onclick="recoverAutosave()">💾 بازیابی</button>
      <button class="btn btn-danger" onclick="discardAutosave()">🗑️ نادیده گرفتن</button>
    </div>
  </div>
</div>

<div class="toast-container" id="toastContainer" aria-live="polite"></div>

<input type="file" id="fileInput" accept=".json" style="display:none;">

<script>
/* ============================================================
   xKanban - Core Application
   Architect: Hadi Khazaee Asl
   Version: 1.0.0
   
   Features:
   - Command Pattern for Undo/Redo
   - Auto-save with recovery
   - Event Delegation for XSS prevention
   - Reference cleanup on delete
   - Search & Filter
   - Statistics Dashboard
   - Export to Markdown/CSV
   - Keyboard Navigation
   - ARIA Accessibility
   - Migration Strategy
   - Webhook Integration
   ============================================================ */

const APP_VERSION = '1.0.0';
const AUTOSAVE_INTERVAL = 30000;
const AUTOSAVE_KEY = 'xkanban-autosave';
const WEBHOOK_STORAGE_KEY = 'xkanban-webhook';

// ============ Event Types ============
const EVENT_TYPES = {
  PROJECT_UPDATED: 'project.updated',
  PROJECT_LOADED: 'project.loaded',
  PROJECT_SAVED: 'project.saved',
  BOARD_CREATED: 'board.created',
  BOARD_UPDATED: 'board.updated',
  BOARD_DELETED: 'board.deleted',
  COLUMN_CREATED: 'column.created',
  COLUMN_UPDATED: 'column.updated',
  COLUMN_DELETED: 'column.deleted',
  COLUMN_REORDERED: 'column.reordered',
  CARD_CREATED: 'card.created',
  CARD_UPDATED: 'card.updated',
  CARD_DELETED: 'card.deleted',
  CARD_MOVED: 'card.moved',
  TAG_CREATED: 'tag.created',
  TAG_DELETED: 'tag.deleted',
  WEBHOOK_TEST: 'webhook.test'
};

// ============ State Management ============
const AppState = {
  project: {
    id: generateId(),
    title: 'پروژه جدید xKanban',
    description: '',
    createdAt: new Date().toISOString(),
    boards: []
  },
  tags: [],
  activeBoardId: null,
  editing: { boardId: null, columnId: null, cardId: null },
  tempData: {},
  search: { query: '', tag: '', assignee: '', status: '' },
  keyboard: { focusedCardId: null },
  webhook: {
    url: '',
    enabled: true,
    lastStatus: null,
    lastSent: null,
    lastError: null,
    stats: { total: 0, success: 0, failed: 0 }
  }
};

// ============ Utilities ============
function generateId() {
  return 'id_' + Date.now().toString(36) + '_' + Math.random().toString(36).substr(2, 9);
}

function formatDate(iso) {
  if (!iso) return '—';
  const d = new Date(iso);
  return d.toLocaleDateString('fa-IR') + ' ' + d.toLocaleTimeString('fa-IR', {hour:'2-digit', minute:'2-digit'});
}

function formatDateShort(iso) {
  if (!iso) return '';
  const d = new Date(iso);
  return d.toLocaleDateString('fa-IR') + ' ' + d.toLocaleTimeString('fa-IR', {hour:'2-digit', minute:'2-digit'});
}

function escapeHtml(str) {
  if (!str) return '';
  return String(str).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
}

function showToast(message, type = 'info') {
  const container = document.getElementById('toastContainer');
  const toast = document.createElement('div');
  toast.className = `toast ${type}`;
  toast.setAttribute('role', 'alert');
  const icons = { success: '✅', error: '❌', warning: '⚠️', info: 'ℹ️' };
  toast.innerHTML = `<span>${icons[type] || 'ℹ️'}</span><span>${escapeHtml(message)}</span>`;
  container.appendChild(toast);
  setTimeout(() => {
    toast.style.opacity = '0';
    toast.style.transform = 'translateX(-30px)';
    setTimeout(() => toast.remove(), 300);
  }, 3000);
}

const COLOR_PALETTE = [
  '#ffffff', '#f7fafc', '#edf2f7', '#e2e8f0',
  '#fef5e7', '#fffaf0', '#fefcbf',
  '#f0fff4', '#e6fffa', '#c6f6d5',
  '#ebf8ff', '#bee3f8', '#90cdf4',
  '#faf5ff', '#e9d8fd', '#d6bcfa',
  '#fed7d7', '#feb2b2', '#fc8181',
  '#1a365d', '#2c5282', '#2b6cb0',
  '#1a202c', '#2d3748', '#4a5568'
];

const TEXT_COLORS = ['#1a202c', '#ffffff', '#2c5282', '#744210', '#22543d', '#553c9a', '#742a2a', '#c9a961', '#e53e3e', '#38a169'];

// ============ Webhook Manager ============
const WebhookManager = {
  async send(eventType, data, options = {}) {
    const url = AppState.webhook.url;
    if (!url || !AppState.webhook.enabled) {
      return { skipped: true, reason: !url ? 'no-url' : 'disabled' };
    }

    const board = getActiveBoard();
    const message = {
      eventId: generateId(),
      timestamp: new Date().toISOString(),
      eventType: eventType,
      version: APP_VERSION,
      source: 'xKanban',
      project: {
        id: AppState.project.id,
        title: AppState.project.title
      },
      board: board ? {
        id: board.id,
        title: board.title
      } : null,
      data: data || {},
      metadata: {
        userAgent: navigator.userAgent,
        language: navigator.language,
        screenResolution: `${screen.width}x${screen.height}`,
        triggeredBy: options.triggeredBy || 'user-action'
      }
    };

    this.showIndicator('pending', 'در حال ارسال...');

    try {
      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), 5000);

      const response = await fetch(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-Webhook-Source': 'xKanban',
          'X-Webhook-Event': eventType,
          'X-Webhook-Version': APP_VERSION
        },
        body: JSON.stringify(message),
        keepalive: true,
        signal: controller.signal
      });

      clearTimeout(timeoutId);

      AppState.webhook.lastStatus = response.ok ? 'success' : 'error';
      AppState.webhook.lastSent = new Date().toISOString();
      AppState.webhook.lastError = response.ok ? null : `HTTP ${response.status}`;
      AppState.webhook.stats.total++;
      if (response.ok) AppState.webhook.stats.success++;
      else AppState.webhook.stats.failed++;

      this.saveToStorage();
      this.showIndicator(response.ok ? 'success' : 'error', 
        response.ok ? `✓ ${eventType}` : `✗ خطای ${response.status}`);
      this.updateStatusUI();

      return { success: response.ok, status: response.status, message };
    } catch (err) {
      AppState.webhook.lastStatus = 'error';
      AppState.webhook.lastSent = new Date().toISOString();
      AppState.webhook.lastError = err.name === 'AbortError' ? 'Timeout' : err.message;
      AppState.webhook.stats.total++;
      AppState.webhook.stats.failed++;

      this.saveToStorage();
      this.showIndicator('error', `✗ خطا: ${AppState.webhook.lastError}`);
      this.updateStatusUI();

      return { success: false, error: err.message, message };
    }
  },

  showIndicator(status, text) {
    const indicator = document.getElementById('webhookIndicator');
    const dot = document.getElementById('webhookIndicatorDot');
    const textEl = document.getElementById('webhookIndicatorText');

    indicator.className = `webhook-indicator visible ${status}`;
    dot.className = `webhook-status-dot ${status}`;
    textEl.textContent = text;

    setTimeout(() => {
      indicator.classList.remove('visible');
    }, 2500);
  },

  updateStatusUI() {
    const statusEl = document.getElementById('webhookStatus');
    const dotEl = document.getElementById('webhookStatusDot');
    const textEl = document.getElementById('webhookStatusText');
    const timeEl = document.getElementById('webhookStatusTime');

    if (!AppState.webhook.lastSent) {
      statusEl.style.display = 'none';
      return;
    }

    statusEl.style.display = 'flex';
    dotEl.className = `webhook-status-dot ${AppState.webhook.lastStatus || ''}`;

    const stats = AppState.webhook.stats;
    if (AppState.webhook.lastStatus === 'success') {
      textEl.textContent = `✓ ارسال موفق (موفق: ${stats.success} | خطا: ${stats.failed})`;
    } else if (AppState.webhook.lastStatus === 'error') {
      textEl.textContent = `✗ ${AppState.webhook.lastError} (موفق: ${stats.success} | خطا: ${stats.failed})`;
    } else {
      textEl.textContent = `در انتظار...`;
    }

    timeEl.textContent = formatDate(AppState.webhook.lastSent);
  },

  saveToStorage() {
    try {
      localStorage.setItem(WEBHOOK_STORAGE_KEY, JSON.stringify({
        url: AppState.webhook.url,
        enabled: AppState.webhook.enabled,
        stats: AppState.webhook.stats,
        lastStatus: AppState.webhook.lastStatus,
        lastSent: AppState.webhook.lastSent,
        lastError: AppState.webhook.lastError
      }));
    } catch (e) {
      console.error('Webhook storage failed:', e);
    }
  },

  loadFromStorage() {
    try {
      const saved = localStorage.getItem(WEBHOOK_STORAGE_KEY);
      if (saved) {
        const data = JSON.parse(saved);
        AppState.webhook.url = data.url || '';
        AppState.webhook.enabled = data.enabled !== false;
        AppState.webhook.stats = data.stats || { total: 0, success: 0, failed: 0 };
        AppState.webhook.lastStatus = data.lastStatus || null;
        AppState.webhook.lastSent = data.lastSent || null;
        AppState.webhook.lastError = data.lastError || null;
      }
    } catch (e) {
      console.error('Webhook load failed:', e);
    }
  },

  async test() {
    const result = await this.send(EVENT_TYPES.WEBHOOK_TEST, {
      message: 'این یک پیام تست از xKanban است',
      testTime: new Date().toISOString()
    }, { triggeredBy: 'manual-test' });

    if (result.skipped) {
      showToast(result.reason === 'no-url' ? 'آدرس وب‌هوک تنظیم نشده است' : 'وب‌هوک غیرفعال است', 'warning');
      return;
    }

    if (result.success) {
      showToast('✓ تست وب‌هوک موفقیت‌آمیز بود', 'success');
    } else {
      showToast(`✗ خطا در تست وب‌هوک: ${result.error || result.status}`, 'error');
    }
  }
};

// ============ Command Pattern (Undo/Redo) ============
const CommandHistory = {
  undoStack: [],
  redoStack: [],
  maxHistory: 50,
  isExecuting: false,
  
  async execute(command) {
    if (this.isExecuting) return;
    try {
      this.isExecuting = true;
      command.execute();
      this.undoStack.push(command);
      if (this.undoStack.length > this.maxHistory) {
        this.undoStack.shift();
      }
      this.redoStack = [];
      this.updateButtons();
      AutoSaveManager.scheduleSave();
      
      // Send webhook
      if (command.eventType) {
        const eventData = typeof command.getEventData === 'function' ? command.getEventData() : {};
        WebhookManager.send(command.eventType, eventData, { triggeredBy: 'command-execute' });
      }
    } finally {
      this.isExecuting = false;
    }
  },
  
  async undo() {
    const cmd = this.undoStack.pop();
    if (!cmd) return;
    try {
      this.isExecuting = true;
      cmd.undo();
      this.redoStack.push(cmd);
      this.updateButtons();
      renderBoard();
      renderTabs();
      showToast('عملیات بازگشت انجام شد', 'info');
      
      // Send webhook
      if (cmd.eventType) {
        WebhookManager.send(cmd.eventType + '.undone', 
          typeof cmd.getEventData === 'function' ? cmd.getEventData() : {},
          { triggeredBy: 'undo' });
      }
    } finally {
      this.isExecuting = false;
    }
  },
  
  async redo() {
    const cmd = this.redoStack.pop();
    if (!cmd) return;
    try {
      this.isExecuting = true;
      cmd.execute();
      this.undoStack.push(cmd);
      this.updateButtons();
      renderBoard();
      renderTabs();
      showToast('عملیات از نو انجام شد', 'info');
      
      // Send webhook
      if (cmd.eventType) {
        WebhookManager.send(cmd.eventType + '.redone',
          typeof cmd.getEventData === 'function' ? cmd.getEventData() : {},
          { triggeredBy: 'redo' });
      }
    } finally {
      this.isExecuting = false;
    }
  },
  
  updateButtons() {
    document.getElementById('undoBtn').disabled = this.undoStack.length === 0;
    document.getElementById('redoBtn').disabled = this.redoStack.length === 0;
  },
  
  clear() {
    this.undoStack = [];
    this.redoStack = [];
    this.updateButtons();
  }
};

// Command classes with webhook event types
class CreateBoardCommand {
  constructor(boardData) {
    this.boardData = boardData;
    this.boardId = null;
  }
  get eventType() { return EVENT_TYPES.BOARD_CREATED; }
  execute() {
    const board = {
      id: generateId(),
      ...this.boardData,
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString(),
      columns: []
    };
    this.boardId = board.id;
    AppState.project.boards.push(board);
    AppState.activeBoardId = board.id;
  }
  undo() {
    AppState.project.boards = AppState.project.boards.filter(b => b.id !== this.boardId);
    if (AppState.activeBoardId === this.boardId) {
      AppState.activeBoardId = AppState.project.boards[0]?.id || null;
    }
  }
  getEventData() {
    return {
      boardId: this.boardId,
      title: this.boardData.title,
      description: this.boardData.description
    };
  }
}

class UpdateBoardCommand {
  constructor(boardId, oldData, newData) {
    this.boardId = boardId;
    this.oldData = oldData;
    this.newData = newData;
  }
  get eventType() { return EVENT_TYPES.BOARD_UPDATED; }
  execute() {
    const board = AppState.project.boards.find(b => b.id === this.boardId);
    if (board) Object.assign(board, this.newData, { updatedAt: new Date().toISOString() });
  }
  undo() {
    const board = AppState.project.boards.find(b => b.id === this.boardId);
    if (board) Object.assign(board, this.oldData, { updatedAt: new Date().toISOString() });
  }
  getEventData() {
    return { boardId: this.boardId, changes: this.newData };
  }
}

class DeleteBoardCommand {
  constructor(boardId) {
    this.boardId = boardId;
    this.boardData = null;
    this.previousIndex = -1;
  }
  get eventType() { return EVENT_TYPES.BOARD_DELETED; }
  execute() {
    const idx = AppState.project.boards.findIndex(b => b.id === this.boardId);
    if (idx >= 0) {
      this.boardData = AppState.project.boards[idx];
      this.previousIndex = idx;
      AppState.project.boards.splice(idx, 1);
      if (AppState.activeBoardId === this.boardId) {
        AppState.activeBoardId = AppState.project.boards[0]?.id || null;
      }
    }
  }
  undo() {
    if (this.boardData) {
      AppState.project.boards.splice(this.previousIndex, 0, this.boardData);
      AppState.activeBoardId = this.boardId;
    }
  }
  getEventData() {
    return {
      boardId: this.boardId,
      title: this.boardData?.title
    };
  }
}

class CreateColumnCommand {
  constructor(boardId, columnData) {
    this.boardId = boardId;
    this.columnData = columnData;
    this.columnId = null;
  }
  get eventType() { return EVENT_TYPES.COLUMN_CREATED; }
  execute() {
    const board = AppState.project.boards.find(b => b.id === this.boardId);
    if (board) {
      const col = { id: generateId(), ...this.columnData, cards: [] };
      this.columnId = col.id;
      board.columns.push(col);
      board.updatedAt = new Date().toISOString();
    }
  }
  undo() {
    const board = AppState.project.boards.find(b => b.id === this.boardId);
    if (board) {
      board.columns = board.columns.filter(c => c.id !== this.columnId);
      board.updatedAt = new Date().toISOString();
    }
  }
  getEventData() {
    return {
      boardId: this.boardId,
      columnId: this.columnId,
      title: this.columnData.title
    };
  }
}

class UpdateColumnCommand {
  constructor(boardId, columnId, oldData, newData) {
    this.boardId = boardId;
    this.columnId = columnId;
    this.oldData = oldData;
    this.newData = newData;
  }
  get eventType() { return EVENT_TYPES.COLUMN_UPDATED; }
  execute() {
    const board = AppState.project.boards.find(b => b.id === this.boardId);
    if (board) {
      const col = board.columns.find(c => c.id === this.columnId);
      if (col) Object.assign(col, this.newData);
      board.updatedAt = new Date().toISOString();
    }
  }
  undo() {
    const board = AppState.project.boards.find(b => b.id === this.boardId);
    if (board) {
      const col = board.columns.find(c => c.id === this.columnId);
      if (col) Object.assign(col, this.oldData);
      board.updatedAt = new Date().toISOString();
    }
  }
  getEventData() {
    return {
      boardId: this.boardId,
      columnId: this.columnId,
      changes: this.newData
    };
  }
}

class DeleteColumnCommand {
  constructor(boardId, columnId) {
    this.boardId = boardId;
    this.columnId = columnId;
    this.columnData = null;
    this.previousIndex = -1;
  }
  get eventType() { return EVENT_TYPES.COLUMN_DELETED; }
  execute() {
    const board = AppState.project.boards.find(b => b.id === this.boardId);
    if (board) {
      const idx = board.columns.findIndex(c => c.id === this.columnId);
      if (idx >= 0) {
        this.columnData = board.columns[idx];
        this.previousIndex = idx;
        board.columns.splice(idx, 1);
        board.updatedAt = new Date().toISOString();
      }
    }
  }
  undo() {
    const board = AppState.project.boards.find(b => b.id === this.boardId);
    if (board && this.columnData) {
      board.columns.splice(this.previousIndex, 0, this.columnData);
      board.updatedAt = new Date().toISOString();
    }
  }
  getEventData() {
    return {
      boardId: this.boardId,
      columnId: this.columnId,
      title: this.columnData?.title,
      cardsCount: this.columnData?.cards?.length || 0
    };
  }
}

class CreateCardCommand {
  constructor(columnId, cardData) {
    this.columnId = columnId;
    this.cardData = cardData;
    this.cardId = null;
    this.boardId = null;
  }
  get eventType() { return EVENT_TYPES.CARD_CREATED; }
  execute() {
    const { column, board } = findCardContext(null, this.columnId);
    if (column && board) {
      this.boardId = board.id;
      const card = {
        id: generateId(),
        ...this.cardData,
        createdAt: new Date().toISOString(),
        updatedAt: new Date().toISOString()
      };
      this.cardId = card.id;
      column.cards.push(card);
      board.updatedAt = new Date().toISOString();
    }
  }
  undo() {
    const { column, board } = findCardContext(null, this.columnId);
    if (column && board) {
      column.cards = column.cards.filter(c => c.id !== this.cardId);
      board.updatedAt = new Date().toISOString();
    }
  }
  getEventData() {
    const col = findColumnById(this.columnId);
    return {
      boardId: this.boardId,
      columnId: this.columnId,
      columnTitle: col?.title,
      cardId: this.cardId,
      title: this.cardData.title
    };
  }
}

class UpdateCardCommand {
  constructor(cardId, oldData, newData) {
    this.cardId = cardId;
    this.oldData = oldData;
    this.newData = newData;
  }
  get eventType() { return EVENT_TYPES.CARD_UPDATED; }
  execute() {
    const { card, board } = findCard(this.cardId);
    if (card) {
      Object.assign(card, this.newData, { updatedAt: new Date().toISOString() });
      if (board) board.updatedAt = new Date().toISOString();
    }
  }
  undo() {
    const { card, board } = findCard(this.cardId);
    if (card) {
      Object.assign(card, this.oldData, { updatedAt: new Date().toISOString() });
      if (board) board.updatedAt = new Date().toISOString();
    }
  }
  getEventData() {
    const { card, column } = findCard(this.cardId);
    return {
      cardId: this.cardId,
      columnId: column?.id,
      columnTitle: column?.title,
      title: card?.title,
      changes: Object.keys(this.newData)
    };
  }
}

class DeleteCardCommand {
  constructor(cardId) {
    this.cardId = cardId;
    this.cardData = null;
    this.columnId = null;
    this.previousIndex = -1;
    this.boardId = null;
  }
  get eventType() { return EVENT_TYPES.CARD_DELETED; }
  execute() {
    const { card, column, board } = findCard(this.cardId);
    if (card && column && board) {
      this.cardData = JSON.parse(JSON.stringify(card));
      this.columnId = column.id;
      this.boardId = board.id;
      this.previousIndex = column.cards.findIndex(c => c.id === this.cardId);
      column.cards = column.cards.filter(c => c.id !== this.cardId);
      ReferenceManager.cleanupCardReferences(this.cardId);
      board.updatedAt = new Date().toISOString();
    }
  }
  undo() {
    const board = AppState.project.boards.find(b => b.id === this.boardId);
    if (board) {
      const column = board.columns.find(c => c.id === this.columnId);
      if (column && this.cardData) {
        column.cards.splice(this.previousIndex, 0, this.cardData);
        board.updatedAt = new Date().toISOString();
      }
    }
  }
  getEventData() {
    return {
      boardId: this.boardId,
      columnId: this.columnId,
      cardId: this.cardId,
      title: this.cardData?.title
    };
  }
}

class MoveCardCommand {
  constructor(cardId, fromColumnId, toColumnId, fromIndex, toIndex) {
    this.cardId = cardId;
    this.fromColumnId = fromColumnId;
    this.toColumnId = toColumnId;
    this.fromIndex = fromIndex;
    this.toIndex = toIndex;
  }
  get eventType() { return EVENT_TYPES.CARD_MOVED; }
  execute() {
    const board = getActiveBoard();
    if (!board) return;
    const fromCol = board.columns.find(c => c.id === this.fromColumnId);
    const toCol = board.columns.find(c => c.id === this.toColumnId);
    if (!fromCol || !toCol) return;
    
    const cardIdx = fromCol.cards.findIndex(c => c.id === this.cardId);
    if (cardIdx < 0) return;
    const [card] = fromCol.cards.splice(cardIdx, 1);
    
    if (this.fromColumnId !== this.toColumnId) {
      card.history = card.history || [];
      card.history.push({
        from: fromCol.title,
        to: toCol.title,
        date: new Date().toISOString()
      });
      card.updatedAt = new Date().toISOString();
    }
    
    toCol.cards.splice(this.toIndex, 0, card);
    board.updatedAt = new Date().toISOString();
  }
  undo() {
    const board = getActiveBoard();
    if (!board) return;
    const fromCol = board.columns.find(c => c.id === this.fromColumnId);
    const toCol = board.columns.find(c => c.id === this.toColumnId);
    if (!fromCol || !toCol) return;
    
    const cardIdx = toCol.cards.findIndex(c => c.id === this.cardId);
    if (cardIdx < 0) return;
    const [card] = toCol.cards.splice(cardIdx, 1);
    
    if (this.fromColumnId !== this.toColumnId && card.history && card.history.length > 0) {
      card.history.pop();
    }
    
    fromCol.cards.splice(this.fromIndex, 0, card);
    board.updatedAt = new Date().toISOString();
  }
  getEventData() {
    const board = getActiveBoard();
    const fromCol = board?.columns.find(c => c.id === this.fromColumnId);
    const toCol = board?.columns.find(c => c.id === this.toColumnId);
    const card = findCardById(this.cardId);
    return {
      cardId: this.cardId,
      cardTitle: card?.title,
      fromColumn: { id: this.fromColumnId, title: fromCol?.title },
      toColumn: { id: this.toColumnId, title: toCol?.title },
      fromIndex: this.fromIndex,
      toIndex: this.toIndex,
      crossColumn: this.fromColumnId !== this.toColumnId
    };
  }
}

class ReorderColumnsCommand {
  constructor(boardId, fromIdx, toIdx) {
    this.boardId = boardId;
    this.fromIdx = fromIdx;
    this.toIdx = toIdx;
  }
  get eventType() { return EVENT_TYPES.COLUMN_REORDERED; }
  execute() {
    const board = AppState.project.boards.find(b => b.id === this.boardId);
    if (!board) return;
    const [moved] = board.columns.splice(this.fromIdx, 1);
    board.columns.splice(this.toIdx, 0, moved);
    board.updatedAt = new Date().toISOString();
  }
  undo() {
    const board = AppState.project.boards.find(b => b.id === this.boardId);
    if (!board) return;
    const [moved] = board.columns.splice(this.toIdx, 1);
    board.columns.splice(this.fromIdx, 0, moved);
    board.updatedAt = new Date().toISOString();
  }
  getEventData() {
    return {
      boardId: this.boardId,
      fromIndex: this.fromIdx,
      toIndex: this.toIdx
    };
  }
}

class AddTagCommand {
  constructor(tagData) {
    this.tagData = tagData;
    this.tagId = null;
  }
  get eventType() { return EVENT_TYPES.TAG_CREATED; }
  execute() {
    const tag = { id: generateId(), ...this.tagData };
    this.tagId = tag.id;
    AppState.tags.push(tag);
  }
  undo() {
    AppState.tags = AppState.tags.filter(t => t.id !== this.tagId);
    AppState.project.boards.forEach(board => {
      board.columns.forEach(col => {
        col.cards.forEach(card => {
          card.tags = (card.tags || []).filter(tid => tid !== this.tagId);
        });
      });
    });
  }
  getEventData() {
    return {
      tagId: this.tagId,
      name: this.tagData.name,
      color: this.tagData.color
    };
  }
}

class DeleteTagCommand {
  constructor(tagId) {
    this.tagId = tagId;
    this.tagData = null;
    this.cardReferences = [];
  }
  get eventType() { return EVENT_TYPES.TAG_DELETED; }
  execute() {
    const tag = AppState.tags.find(t => t.id === this.tagId);
    if (tag) {
      this.tagData = tag;
      AppState.tags = AppState.tags.filter(t => t.id !== this.tagId);
      AppState.project.boards.forEach(board => {
        board.columns.forEach(col => {
          col.cards.forEach(card => {
            if ((card.tags || []).includes(this.tagId)) {
              this.cardReferences.push({ cardId: card.id, tagId: this.tagId });
              card.tags = card.tags.filter(tid => tid !== this.tagId);
            }
          });
        });
      });
    }
  }
  undo() {
    if (this.tagData) {
      AppState.tags.push(this.tagData);
      this.cardReferences.forEach(ref => {
        const { card } = findCard(ref.cardId);
        if (card) {
          card.tags = card.tags || [];
          card.tags.push(ref.tagId);
        }
      });
    }
  }
  getEventData() {
    return {
      tagId: this.tagId,
      name: this.tagData?.name,
      affectedCards: this.cardReferences.length
    };
  }
}

// ============ Reference Manager ============
const ReferenceManager = {
  cleanupCardReferences(deletedCardId) {
    AppState.project.boards.forEach(board => {
      board.columns.forEach(col => {
        col.cards.forEach(card => {
          if (card.dependencies && card.dependencies.includes(deletedCardId)) {
            card.dependencies = card.dependencies.filter(d => d !== deletedCardId);
          }
        });
      });
    });
  },
  
  cleanupTagReferences(deletedTagId) {
    AppState.project.boards.forEach(board => {
      board.columns.forEach(col => {
        col.cards.forEach(card => {
          if (card.tags && card.tags.includes(deletedTagId)) {
            card.tags = card.tags.filter(t => t !== deletedTagId);
          }
        });
      });
    });
  }
};

// ============ Auto-Save Manager ============
const AutoSaveManager = {
  saveTimer: null,
  lastSaveTime: null,
  
  scheduleSave() {
    if (this.saveTimer) clearTimeout(this.saveTimer);
    this.saveTimer = setTimeout(() => this.save(), AUTOSAVE_INTERVAL);
  },
  
  save() {
    try {
      const data = {
        version: APP_VERSION,
        project: AppState.project,
        tags: AppState.tags,
        activeBoardId: AppState.activeBoardId,
        savedAt: new Date().toISOString()
      };
      localStorage.setItem(AUTOSAVE_KEY, JSON.stringify(data));
      this.lastSaveTime = new Date();
      this.showIndicator();
    } catch (e) {
      console.error('Auto-save failed:', e);
    }
  },
  
  showIndicator() {
    const indicator = document.getElementById('autosaveIndicator');
    const text = document.getElementById('autosaveText');
    text.textContent = 'ذخیره خودکار انجام شد';
    indicator.classList.add('visible');
    setTimeout(() => indicator.classList.remove('visible'), 2000);
  },
  
  load() {
    const saved = localStorage.getItem(AUTOSAVE_KEY);
    if (!saved) return null;
    try {
      return JSON.parse(saved);
    } catch (e) {
      return null;
    }
  },
  
  clear() {
    localStorage.removeItem(AUTOSAVE_KEY);
  }
};

// ============ Migration Manager ============
const MigrationManager = {
  migrations: {
    '1.0.0': (data) => data,
  },
  
  migrate(data) {
    if (!data.version) data.version = '1.0.0';
    
    const versions = Object.keys(this.migrations).sort();
    const currentIdx = versions.indexOf(data.version);
    
    for (let i = currentIdx + 1; i < versions.length; i++) {
      const version = versions[i];
      data = this.migrations[version](data);
      data.version = version;
    }
    
    return data;
  }
};

// ============ Statistics Manager ============
const StatsManager = {
  calculate() {
    const board = getActiveBoard();
    if (!board) return { total: 0, done: 0, soon: 0, overdue: 0, progress: 0 };
    
    let total = 0, done = 0, soon = 0, overdue = 0;
    const now = new Date();
    
    board.columns.forEach((col, idx) => {
      col.cards.forEach(card => {
        total++;
        if (idx === 0) done++;
        
        if (card.estimatedDate) {
          const due = new Date(card.estimatedDate);
          const diffDays = (due - now) / (1000 * 60 * 60 * 24);
          if (diffDays < 0) overdue++;
          else if (diffDays <= 3) soon++;
        }
      });
    });
    
    const progress = total > 0 ? Math.round((done / total) * 100) : 0;
    return { total, done, soon, overdue, progress };
  },
  
  update() {
    const stats = this.calculate();
    document.getElementById('statTotal').textContent = stats.total.toLocaleString('fa-IR');
    document.getElementById('statDone').textContent = stats.done.toLocaleString('fa-IR');
    document.getElementById('statSoon').textContent = stats.soon.toLocaleString('fa-IR');
    document.getElementById('statOverdue').textContent = stats.overdue.toLocaleString('fa-IR');
    document.getElementById('statProgress').textContent = stats.progress.toLocaleString('fa-IR') + '%';
  }
};

// ============ Search/Filter Manager ============
const SearchManager = {
  apply() {
    AppState.search.query = document.getElementById('searchInput').value.trim().toLowerCase();
    AppState.search.tag = document.getElementById('filterTag').value;
    AppState.search.assignee = document.getElementById('filterAssignee').value;
    AppState.search.status = document.getElementById('filterStatus').value;
    this.applyVisualFilter();
    this.updateStats();
  },
  
  matchesCard(card) {
    const { query, tag, assignee, status } = AppState.search;
    
    if (query) {
      const title = (card.title || '').toLowerCase();
      const desc = (card.description || '').toLowerCase();
      if (!title.includes(query) && !desc.includes(query)) return false;
    }
    
    if (tag && !(card.tags || []).includes(tag)) return false;
    
    if (assignee) {
      const hasAssignee = (card.assignees || []).some(a => 
        a.id === assignee || a.name === assignee
      );
      if (!hasAssignee) return false;
    }
    
    if (status) {
      const cardStatus = this.getCardStatus(card);
      if (cardStatus !== status) return false;
    }
    
    return true;
  },
  
  getCardStatus(card) {
    if (!card.estimatedDate) return 'no-date';
    const due = new Date(card.estimatedDate);
    const now = new Date();
    const diffDays = (due - now) / (1000 * 60 * 60 * 24);
    if (diffDays < 0) return 'overdue';
    if (diffDays <= 3) return 'soon';
    return 'normal';
  },
  
  applyVisualFilter() {
    const board = getActiveBoard();
    if (!board) return;
    
    const hasFilter = AppState.search.query || AppState.search.tag || 
                      AppState.search.assignee || AppState.search.status;
    
    document.querySelectorAll('.card').forEach(cardEl => {
      const cardId = cardEl.dataset.cardId;
      const card = findCardById(cardId);
      if (!card) return;
      
      if (hasFilter && !this.matchesCard(card)) {
        cardEl.classList.add('filtered-out');
      } else {
        cardEl.classList.remove('filtered-out');
      }
    });
    
    document.querySelectorAll('.column').forEach(colEl => {
      const colId = colEl.dataset.columnId;
      const col = board.columns.find(c => c.id === colId);
      if (!col) return;
      
      if (hasFilter) {
        const visibleCards = col.cards.filter(c => this.matchesCard(c));
        if (visibleCards.length === 0) {
          colEl.classList.add('filtered-out');
        } else {
          colEl.classList.remove('filtered-out');
        }
      } else {
        colEl.classList.remove('filtered-out');
      }
    });
  },
  
  updateStats() {
    const board = getActiveBoard();
    if (!board) return;
    
    let visibleCount = 0;
    board.columns.forEach(col => {
      col.cards.forEach(card => {
        if (this.matchesCard(card)) visibleCount++;
      });
    });
    
    const statsEl = document.getElementById('searchStats');
    const hasFilter = AppState.search.query || AppState.search.tag || 
                      AppState.search.assignee || AppState.search.status;
    
    if (hasFilter) {
      statsEl.textContent = `${visibleCount.toLocaleString('fa-IR')} کارت یافت شد`;
    } else {
      const total = board.columns.reduce((sum, col) => sum + col.cards.length, 0);
      statsEl.textContent = `${total.toLocaleString('fa-IR')} کارت`;
    }
  },
  
  updateFilterOptions() {
    const tagSelect = document.getElementById('filterTag');
    const currentTag = tagSelect.value;
    tagSelect.innerHTML = '<option value="">🏷️ همه تگ‌ها</option>';
    AppState.tags.forEach(tag => {
      const opt = document.createElement('option');
      opt.value = tag.id;
      opt.textContent = tag.name;
      tagSelect.appendChild(opt);
    });
    tagSelect.value = currentTag;
    
    const assigneeSelect = document.getElementById('filterAssignee');
    const currentAssignee = assigneeSelect.value;
    const assignees = new Map();
    const board = getActiveBoard();
    if (board) {
      board.columns.forEach(col => {
        col.cards.forEach(card => {
          (card.assignees || []).forEach(a => {
            assignees.set(a.name, a);
          });
        });
      });
    }
    assigneeSelect.innerHTML = '<option value="">👥 همه مسئولین</option>';
    assignees.forEach((a, name) => {
      const opt = document.createElement('option');
      opt.value = a.id;
      opt.textContent = name;
      assigneeSelect.appendChild(opt);
    });
    assigneeSelect.value = currentAssignee;
  }
};

// ============ Export Manager ============
const ExportManager = {
  toMarkdown(boardId) {
    const boards = boardId === 'all' 
      ? AppState.project.boards 
      : AppState.project.boards.filter(b => b.id === boardId);
    
    let md = `# ${AppState.project.title}\n\n`;
    md += `${AppState.project.description || ''}\n\n`;
    md += `---\n\n`;
    md += `*تاریخ ایجاد: ${formatDate(AppState.project.createdAt)}*\n\n`;
    
    boards.forEach(board => {
      md += `## 📋 ${board.title}\n\n`;
      if (board.description) md += `${board.description}\n\n`;
      
      board.columns.forEach(col => {
        md += `### 📑 ${col.title} (${col.cards.length})\n\n`;
        
        if (col.cards.length === 0) {
          md += `_خالی_\n\n`;
        } else {
          col.cards.forEach(card => {
            md += `#### ${card.title}\n\n`;
            md += `${card.description}\n\n`;
            
            if (card.estimatedDate) {
              md += `**📅 تاریخ:** ${formatDate(card.estimatedDate)}\n\n`;
            }
            
            if (card.assignees && card.assignees.length > 0) {
              md += `**👥 مسئولین:** ${card.assignees.map(a => a.name).join('، ')}\n\n`;
            }
            
            if (card.tags && card.tags.length > 0) {
              const tagNames = card.tags.map(tid => {
                const tag = AppState.tags.find(t => t.id === tid);
                return tag ? tag.name : null;
              }).filter(Boolean);
              if (tagNames.length > 0) {
                md += `**🏷️ برچسب‌ها:** ${tagNames.join('، ')}\n\n`;
              }
            }
            
            if (card.checklist && card.checklist.length > 0) {
              md += `**✅ چک‌لیست:**\n\n`;
              card.checklist.forEach(item => {
                md += `- [${item.done ? 'x' : ' '}] ${item.title}\n`;
              });
              md += `\n`;
            }
            
            md += `---\n\n`;
          });
        }
      });
    });
    
    return md;
  },
  
  toCSV(boardId) {
    const boards = boardId === 'all' 
      ? AppState.project.boards 
      : AppState.project.boards.filter(b => b.id === boardId);
    
    const headers = [
      'برد', 'ستون', 'عنوان کارت', 'توضیحات', 
      'تاریخ تخمینی', 'مسئولین', 'برچسب‌ها', 
      'چک‌لیست (تکمیل/کل)', 'وابستگی‌ها', 'تاریخ ایجاد'
    ];
    
    const rows = [headers];
    
    boards.forEach(board => {
      board.columns.forEach(col => {
        col.cards.forEach(card => {
          const assignees = (card.assignees || []).map(a => a.name).join(' | ');
          const tags = (card.tags || []).map(tid => {
            const tag = AppState.tags.find(t => t.id === tid);
            return tag ? tag.name : '';
          }).filter(Boolean).join(' | ');
          
          const checklistTotal = (card.checklist || []).length;
          const checklistDone = (card.checklist || []).filter(i => i.done).length;
          const checklist = `${checklistDone}/${checklistTotal}`;
          
          const deps = (card.dependencies || []).map(depId => {
            const dep = findCardById(depId);
            return dep ? dep.title : '';
          }).filter(Boolean).join(' | ');
          
          rows.push([
            board.title,
            col.title,
            card.title,
            (card.description || '').replace(/\n/g, ' '),
            card.estimatedDate ? formatDate(card.estimatedDate) : '',
            assignees,
            tags,
            checklist,
            deps,
            card.createdAt ? formatDate(card.createdAt) : ''
          ]);
        });
      });
    });
    
    return rows.map(row => 
      row.map(cell => `"${String(cell).replace(/"/g, '""')}"`).join(',')
    ).join('\n');
  },
  
  download(content, filename, mimeType) {
    const blob = new Blob([content], { type: mimeType + ';charset=utf-8' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = filename;
    a.click();
    URL.revokeObjectURL(url);
  }
};

// ============ Keyboard Navigation Manager ============
const KeyboardManager = {
  focusedCardId: null,
  
  handleKey(e) {
    if (document.querySelector('.modal-overlay.active')) return;
    if (['INPUT', 'TEXTAREA', 'SELECT'].includes(document.activeElement.tagName)) {
      if (e.key === 'Escape') document.activeElement.blur();
      return;
    }
    
    const board = getActiveBoard();
    if (!board) return;
    
    const allCards = [];
    board.columns.forEach(col => {
      col.cards.forEach(card => allCards.push({ card, column: col }));
    });
    
    if (allCards.length === 0) return;
    
    switch(e.key) {
      case 'Tab':
        e.preventDefault();
        this.focusNextCard(allCards, e.shiftKey ? -1 : 1);
        break;
      case 'ArrowRight':
        if (e.altKey && this.focusedCardId) {
          e.preventDefault();
          this.moveCardToAdjacentColumn(this.focusedCardId, -1);
        }
        break;
      case 'ArrowLeft':
        if (e.altKey && this.focusedCardId) {
          e.preventDefault();
          this.moveCardToAdjacentColumn(this.focusedCardId, 1);
        }
        break;
      case 'Enter':
      case ' ':
        if (this.focusedCardId) {
          e.preventDefault();
          openEditCardModal(this.focusedCardId);
        }
        break;
      case 'Escape':
        this.clearFocus();
        break;
    }
  },
  
  focusNextCard(allCards, direction) {
    let currentIdx = -1;
    if (this.focusedCardId) {
      currentIdx = allCards.findIndex(c => c.card.id === this.focusedCardId);
    }
    
    let nextIdx = currentIdx + direction;
    if (nextIdx >= allCards.length) nextIdx = 0;
    if (nextIdx < 0) nextIdx = allCards.length - 1;
    
    this.clearFocus();
    this.focusedCardId = allCards[nextIdx].card.id;
    
    const cardEl = document.querySelector(`[data-card-id="${this.focusedCardId}"]`);
    if (cardEl) {
      cardEl.classList.add('keyboard-focused');
      cardEl.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
    }
  },
  
  moveCardToAdjacentColumn(cardId, direction) {
    const board = getActiveBoard();
    if (!board) return;
    
    let currentColIdx = -1, cardIdx = -1, card = null;
    
    for (let i = 0; i < board.columns.length; i++) {
      const col = board.columns[i];
      const idx = col.cards.findIndex(c => c.id === cardId);
      if (idx >= 0) {
        currentColIdx = i;
        cardIdx = idx;
        card = col.cards[idx];
        break;
      }
    }
    
    if (currentColIdx < 0) return;
    
    const targetColIdx = currentColIdx + direction;
    if (targetColIdx < 0 || targetColIdx >= board.columns.length) {
      showToast('ستون دیگری در این جهت وجود ندارد', 'warning');
      return;
    }
    
    const fromCol = board.columns[currentColIdx];
    const toCol = board.columns[targetColIdx];
    
    const cmd = new MoveCardCommand(
      cardId, fromCol.id, toCol.id, cardIdx, toCol.cards.length
    );
    CommandHistory.execute(cmd);
    renderBoard();
    showToast(`کارت به "${toCol.title}" منتقل شد`, 'success');
  },
  
  clearFocus() {
    if (this.focusedCardId) {
      const el = document.querySelector(`[data-card-id="${this.focusedCardId}"]`);
      if (el) el.classList.remove('keyboard-focused');
      this.focusedCardId = null;
    }
  }
};

// ============ Theme Management ============
function toggleTheme() {
  const current = document.documentElement.getAttribute('data-theme');
  const newTheme = current === 'dark' ? 'light' : 'dark';
  document.documentElement.setAttribute('data-theme', newTheme);
  localStorage.setItem('xkanban-theme', newTheme);
  updateThemeUI(newTheme);
}

function updateThemeUI(theme) {
  const btn = document.getElementById('themeToggleBtn');
  const icon = document.getElementById('themeIcon');
  const text = document.getElementById('themeText');
  if (theme === 'dark') {
    btn.textContent = '☀️';
    btn.title = 'تغییر به حالت روشن';
    if (icon) icon.textContent = '☀️';
    if (text) text.textContent = 'حالت روشن';
  } else {
    btn.textContent = '🌙';
    btn.title = 'تغییر به حالت تاریک';
    if (icon) icon.textContent = '🌙';
    if (text) text.textContent = 'حالت تاریک';
  }
}

function initTheme() {
  const saved = localStorage.getItem('xkanban-theme') || 'dark';
  document.documentElement.setAttribute('data-theme', saved);
  updateThemeUI(saved);
}

// ============ Sidebar Management ============
function updateSidebarToggleIcon() {
  const sidebar = document.getElementById('sidebar');
  const btn = document.getElementById('sidebarToggleBtn');
  if (!btn) return;
  
  const isCollapsed = sidebar.classList.contains('collapsed');
  if (isCollapsed) {
    btn.textContent = '☰';
    btn.title = 'باز کردن نوار ابزار';
  } else {
    btn.textContent = '✕';
    btn.title = 'بستن نوار ابزار';
  }
}

function toggleSidebar() {
  const sidebar = document.getElementById('sidebar');
  sidebar.classList.toggle('collapsed');
  updateSidebarToggleIcon();
}

function toggleGroup(groupName) {
  const group = document.querySelector(`.sidebar-group[data-group="${groupName}"]`);
  if (group) {
    group.classList.toggle('collapsed');
    const header = group.querySelector('.sidebar-group-header');
    if (header) {
      header.setAttribute('aria-expanded', !group.classList.contains('collapsed'));
    }
  }
}

// ============ Modal Management ============
function openModal(id) { document.getElementById(id).classList.add('active'); }
function closeModal(id) { document.getElementById(id).classList.remove('active'); }

document.querySelectorAll('.modal-overlay').forEach(overlay => {
  overlay.addEventListener('click', (e) => {
    if (e.target === overlay) overlay.classList.remove('active');
  });
});

// ============ Project Settings ============
function openProjectSettings() {
  document.getElementById('projectTitle').value = AppState.project.title;
  document.getElementById('projectDesc').value = AppState.project.description;
  document.getElementById('projectCreatedAt').value = formatDate(AppState.project.createdAt);
  
  // Load webhook settings
  document.getElementById('webhookUrl').value = AppState.webhook.url;
  document.getElementById('webhookEnabled').checked = AppState.webhook.enabled;
  WebhookManager.updateStatusUI();
  
  openModal('projectModal');
}

function saveProjectSettings() {
  const title = document.getElementById('projectTitle').value.trim();
  if (!title) { showToast('عنوان پروژه الزامی است', 'error'); return; }
  
  const oldData = {
    title: AppState.project.title,
    description: AppState.project.description
  };
  const newData = {
    title,
    description: document.getElementById('projectDesc').value.trim()
  };
  
  // Save webhook settings
  const webhookUrl = document.getElementById('webhookUrl').value.trim();
  const webhookEnabled = document.getElementById('webhookEnabled').checked;
  
  const webhookChanged = AppState.webhook.url !== webhookUrl || 
                         AppState.webhook.enabled !== webhookEnabled;
  
  AppState.webhook.url = webhookUrl;
  AppState.webhook.enabled = webhookEnabled;
  WebhookManager.saveToStorage();
  
  // Direct update for project settings
  AppState.project.title = newData.title;
  AppState.project.description = newData.description;
  closeModal('projectModal');
  showToast('تنظیمات پروژه ذخیره شد', 'success');
  AutoSaveManager.scheduleSave();
  
  // Send webhooks
  WebhookManager.send(EVENT_TYPES.PROJECT_UPDATED, {
    changes: Object.keys(newData),
    webhookConfigChanged: webhookChanged,
    webhookUrl: webhookUrl,
    webhookEnabled: webhookEnabled
  }, { triggeredBy: 'settings-save' });
}

async function testWebhook() {
  const url = document.getElementById('webhookUrl').value.trim();
  const enabled = document.getElementById('webhookEnabled').checked;
  
  // Temporarily set for test
  const prevUrl = AppState.webhook.url;
  const prevEnabled = AppState.webhook.enabled;
  AppState.webhook.url = url;
  AppState.webhook.enabled = enabled;
  
  const btn = document.getElementById('testWebhookBtn');
  btn.disabled = true;
  btn.textContent = '⏳ در حال تست...';
  
  await WebhookManager.test();
  
  btn.disabled = false;
  btn.textContent = '🧪 تست';
  
  // Restore if not saved yet
  if (!document.getElementById('projectModal').classList.contains('active')) {
    // Modal closed, keep current
  }
}

// ============ Tag Management ============
function openTagManager() {
  renderTagManageList();
  openModal('tagModal');
}

function renderTagManageList() {
  const list = document.getElementById('tagManageList');
  if (AppState.tags.length === 0) {
    list.innerHTML = '<div class="empty-state"><div class="empty-state-icon">🏷️</div><div>هنوز تگی ایجاد نشده</div></div>';
    return;
  }
  list.innerHTML = AppState.tags.map(tag => `
    <div class="tag-manage-item">
      <div class="tag-color-preview" style="background:${tag.color};"></div>
      <div class="tag-manage-name">${escapeHtml(tag.name)}</div>
      <button class="btn btn-sm btn-danger" data-action="delete-tag" data-id="${tag.id}" aria-label="حذف تگ ${escapeHtml(tag.name)}">🗑️</button>
    </div>
  `).join('');
}

function addTag() {
  const name = document.getElementById('newTagName').value.trim();
  const color = document.getElementById('newTagColor').value;
  if (!name) { showToast('نام تگ الزامی است', 'error'); return; }
  if (AppState.tags.some(t => t.name === name)) {
    showToast('تگی با این نام وجود دارد', 'warning'); return;
  }
  
  const cmd = new AddTagCommand({ name, color });
  CommandHistory.execute(cmd);
  document.getElementById('newTagName').value = '';
  renderTagManageList();
  SearchManager.updateFilterOptions();
  showToast('تگ اضافه شد', 'success');
}

function deleteTag(id) {
  if (!confirm('آیا از حذف این تگ مطمئن هستید؟')) return;
  const cmd = new DeleteTagCommand(id);
  CommandHistory.execute(cmd);
  renderTagManageList();
  renderBoard();
  SearchManager.updateFilterOptions();
  showToast('تگ حذف شد', 'success');
}

// ============ Color Picker ============
function renderColorPicker(containerId, colors, selected, callbackName) {
  const container = document.getElementById(containerId);
  container.innerHTML = colors.map(c => `
    <div class="color-swatch ${c === selected ? 'selected' : ''}" 
         style="background:${c};${c === '#ffffff' ? 'border:1px solid var(--border);' : ''}" 
         data-action="select-color"
         data-container="${containerId}"
         data-color="${c}"
         data-callback="${callbackName}"
         role="button"
         aria-label="انتخاب رنگ ${c}"
         tabindex="0"></div>
  `).join('');
}

function selectColor(containerId, color, callbackName) {
  const container = document.getElementById(containerId);
  container.querySelectorAll('.color-swatch').forEach(s => s.classList.remove('selected'));
  const swatches = container.querySelectorAll('.color-swatch');
  swatches.forEach(s => {
    if (s.dataset.color === color) s.classList.add('selected');
  });
  if (window[callbackName]) window[callbackName](color);
}

function selectCustomColor(type, prop, color) {
  if (type === 'board') {
    if (prop === 'bg') AppState.tempData.boardBg = color;
  } else if (type === 'column') {
    if (prop === 'bg') AppState.tempData.columnBg = color;
    else if (prop === 'text') AppState.tempData.columnText = color;
  } else if (type === 'card') {
    if (!AppState.tempData.card) AppState.tempData.card = {};
    AppState.tempData.card.backgroundColor = color;
  }
}

// ============ Board Management ============
function openNewBoardModal() {
  AppState.editing.boardId = null;
  document.getElementById('boardModalTitle').textContent = 'ایجاد برد جدید';
  document.getElementById('boardTitle').value = '';
  document.getElementById('boardDesc').value = '';
  document.getElementById('boardTitleError').style.display = 'none';
  const defaultBg = document.documentElement.getAttribute('data-theme') === 'dark' ? '#2d3748' : '#e2e8f0';
  AppState.tempData.boardBg = defaultBg;
  renderColorPicker('boardBgColors', COLOR_PALETTE, AppState.tempData.boardBg, 'setBoardBg');
  document.getElementById('boardBgCustom').value = defaultBg;
  openModal('boardModal');
  document.getElementById('boardTitle').focus();
}

function setBoardBg(c) { AppState.tempData.boardBg = c; document.getElementById('boardBgCustom').value = c; }

function editCurrentBoard() {
  if (!AppState.activeBoardId) { showToast('بردی برای ویرایش وجود ندارد', 'warning'); return; }
  openEditBoardModal(AppState.activeBoardId);
}

function openEditBoardModal(boardId) {
  const board = AppState.project.boards.find(b => b.id === boardId);
  if (!board) return;
  AppState.editing.boardId = boardId;
  document.getElementById('boardModalTitle').textContent = 'ویرایش برد';
  document.getElementById('boardTitle').value = board.title;
  document.getElementById('boardDesc').value = board.description;
  document.getElementById('boardTitleError').style.display = 'none';
  const defaultBg = document.documentElement.getAttribute('data-theme') === 'dark' ? '#2d3748' : '#e2e8f0';
  AppState.tempData.boardBg = board.backgroundColor || defaultBg;
  renderColorPicker('boardBgColors', COLOR_PALETTE, AppState.tempData.boardBg, 'setBoardBg');
  document.getElementById('boardBgCustom').value = AppState.tempData.boardBg;
  openModal('boardModal');
}

function validateBoardTitle(title, excludeId = null) {
  if (!title.trim()) return 'عنوان برد الزامی است';
  const exists = AppState.project.boards.some(b => 
    b.title.trim().toLowerCase() === title.trim().toLowerCase() && 
    b.id !== excludeId
  );
  if (exists) return 'بردی با این عنوان وجود دارد';
  return null;
}

function saveBoard() {
  const title = document.getElementById('boardTitle').value.trim();
  const error = validateBoardTitle(title, AppState.editing.boardId);
  if (error) {
    const errorEl = document.getElementById('boardTitleError');
    errorEl.textContent = error;
    errorEl.style.display = 'block';
    return;
  }
  
  const desc = document.getElementById('boardDesc').value.trim();
  
  if (AppState.editing.boardId) {
    const board = AppState.project.boards.find(b => b.id === AppState.editing.boardId);
    const oldData = {
      title: board.title,
      description: board.description,
      backgroundColor: board.backgroundColor
    };
    const newData = {
      title,
      description: desc,
      backgroundColor: AppState.tempData.boardBg
    };
    const cmd = new UpdateBoardCommand(AppState.editing.boardId, oldData, newData);
    CommandHistory.execute(cmd);
    showToast('برد بروزرسانی شد', 'success');
  } else {
    const cmd = new CreateBoardCommand({
      title,
      description: desc,
      backgroundColor: AppState.tempData.boardBg
    });
    CommandHistory.execute(cmd);
    showToast('برد ایجاد شد', 'success');
  }
  closeModal('boardModal');
  renderTabs();
  renderBoard();
}

function deleteCurrentBoard() {
  if (!AppState.activeBoardId) { showToast('بردی برای حذف وجود ندارد', 'warning'); return; }
  deleteBoard(AppState.activeBoardId);
}

function deleteBoard(boardId) {
  const board = AppState.project.boards.find(b => b.id === boardId);
  if (!board) return;
  if (!confirm(`آیا از حذف برد "${board.title}" و تمام محتویات آن مطمئن هستید؟\nاین عمل قابل بازگشت نیست.`)) return;
  
  const cmd = new DeleteBoardCommand(boardId);
  CommandHistory.execute(cmd);
  renderTabs();
  renderBoard();
  showToast('برد حذف شد', 'success');
}

function switchBoard(boardId) {
  AppState.activeBoardId = boardId;
  KeyboardManager.clearFocus();
  renderTabs();
  renderBoard();
}

// ============ Tabs Rendering ============
function renderTabs() {
  const tabsList = document.getElementById('tabsList');
  if (AppState.project.boards.length === 0) {
    tabsList.innerHTML = '<div style="color:rgba(255,255,255,0.5);font-size:12px;padding:0 10px;">هیچ بردی ایجاد نشده است</div>';
    return;
  }
  tabsList.innerHTML = AppState.project.boards.map(board => `
    <button class="tab-item ${board.id === AppState.activeBoardId ? 'active' : ''}" 
            data-action="switch-board" 
            data-id="${board.id}"
            role="tab"
            aria-selected="${board.id === AppState.activeBoardId}">
      <span>📋</span>
      <span>${escapeHtml(board.title)}</span>
      <button class="tab-close" data-action="delete-board" data-id="${board.id}" title="حذف برد" aria-label="حذف برد ${escapeHtml(board.title)}">✕</button>
    </button>
  `).join('');
}

// ============ Board Rendering ============
function renderBoard() {
  const empty = document.getElementById('boardEmpty');
  const wrapper = document.getElementById('boardWrapper');
  const boardArea = document.getElementById('boardArea');
  const board = AppState.project.boards.find(b => b.id === AppState.activeBoardId);

  if (!board) {
    empty.style.display = 'flex';
    wrapper.style.display = 'none';
    boardArea.style.background = '';
    StatsManager.update();
    SearchManager.updateStats();
    return;
  }

  empty.style.display = 'none';
  wrapper.style.display = 'flex';
  
  const boardBg = board.backgroundColor || '';
  boardArea.style.background = boardBg || '';

  wrapper.innerHTML = board.columns.map(col => renderColumn(col, board)).join('') + `
    <button class="add-column-btn" data-action="new-column" aria-label="افزودن ستون جدید">
      ➕ افزودن ستون جدید
    </button>
  `;

  attachColumnDragEvents();
  attachCardDragEvents();
  StatsManager.update();
  SearchManager.updateFilterOptions();
  SearchManager.apply();
}

function renderColumn(col, board) {
  const cardCount = col.cards.length;
  const colBg = col.backgroundColor || '';
  const colText = col.textColor || '';
  const colStyle = [];
  if (colBg) colStyle.push(`background:${colBg};`);
  if (colText) colStyle.push(`color:${colText};`);
  const styleStr = colStyle.join('');
  
  return `
    <div class="column" data-column-id="${col.id}" draggable="false" style="${styleStr}">
      <div class="column-header" draggable="true" data-col-drag="${col.id}">
        <div class="column-title">
          <span>${escapeHtml(col.title)}</span>
          <span class="column-count">${cardCount}</span>
        </div>
        <div class="column-actions">
          <button class="column-action-btn" data-action="edit-column" data-id="${col.id}" title="ویرایش" aria-label="ویرایش ستون">✏️</button>
          <button class="column-action-btn" data-action="delete-column" data-id="${col.id}" title="حذف" aria-label="حذف ستون">🗑️</button>
        </div>
      </div>
      <div class="column-body" data-column-id="${col.id}">
        ${col.cards.length === 0 ? `
          <div class="empty-state" style="padding:20px 10px;">
            <div style="font-size:30px;opacity:0.3;">📭</div>
            <div style="font-size:11px;margin-top:6px;">کارتی وجود ندارد</div>
          </div>
        ` : col.cards.map(card => renderCard(card, col)).join('')}
      </div>
      <div class="column-footer">
        <button class="add-card-btn" data-action="new-card" data-column-id="${col.id}" aria-label="افزودن کارت">➕ افزودن کارت</button>
      </div>
    </div>
  `;
}

function renderCard(card, column) {
  const tagHtml = (card.tags || []).map(tid => {
    const tag = AppState.tags.find(t => t.id === tid);
    if (!tag) return '';
    return `<span class="card-tag" style="background:${tag.color};">${escapeHtml(tag.name)}</span>`;
  }).join('');

  const assigneesHtml = (card.assignees || []).map(a => {
    const initial = (a.name || '?').charAt(0).toUpperCase();
    return `<div class="assignee-avatar" title="${escapeHtml(a.name)}">${escapeHtml(initial)}</div>`;
  }).join('');

  const checklistTotal = (card.checklist || []).length;
  const checklistDone = (card.checklist || []).filter(i => i.done).length;
  const checklistHtml = checklistTotal > 0 ? `
    <div class="card-checklist-progress" title="${checklistDone}/${checklistTotal}">
      <div class="card-checklist-bar" style="width:${(checklistDone/checklistTotal)*100}%;"></div>
    </div>
  ` : '';

  const depCount = (card.dependencies || []).length;
  const depHtml = depCount > 0 ? `<div class="card-dep-indicator" title="${depCount} وابستگی">🔗 ${depCount}</div>` : '';

  let dateClass = '';
  let dateText = '';
  if (card.estimatedDate) {
    const due = new Date(card.estimatedDate);
    const now = new Date();
    const diff = (due - now) / (1000*60*60*24);
    dateText = formatDateShort(card.estimatedDate);
    if (diff < 0) dateClass = 'overdue';
    else if (diff <= 3) dateClass = 'soon';
  }

  const bgStyle = card.backgroundColor ? `background:${card.backgroundColor};` : '';
  const textStyle = card.textColor ? `color:${card.textColor};` : '';

  return `
    <div class="card" draggable="true" data-card-id="${card.id}" style="${bgStyle}${textStyle}" 
         data-action="edit-card" data-id="${card.id}"
         role="button" tabindex="0"
         aria-label="${escapeHtml(card.title)}">
      ${depHtml}
      ${tagHtml ? `<div class="card-tags">${tagHtml}</div>` : ''}
      <div class="card-title">${escapeHtml(card.title)}</div>
      ${card.description ? `<div class="card-desc">${escapeHtml(card.description)}</div>` : ''}
      <div class="card-meta">
        ${card.estimatedDate ? `<span class="card-meta-item ${dateClass}">📅 ${dateText}</span>` : ''}
        ${checklistTotal > 0 ? `<span class="card-meta-item">✅ ${checklistDone}/${checklistTotal}</span>` : ''}
        ${(card.assignees || []).length > 0 ? `<div class="card-assignees">${assigneesHtml}</div>` : ''}
      </div>
      ${checklistHtml}
    </div>
  `;
}

// ============ Column Management ============
function openNewColumnModal() {
  if (!AppState.activeBoardId) { showToast('ابتدا یک برد ایجاد کنید', 'warning'); return; }
  AppState.editing.columnId = null;
  document.getElementById('columnModalTitle').textContent = 'ایجاد ستون جدید';
  document.getElementById('columnTitle').value = '';
  document.getElementById('columnTitleError').style.display = 'none';
  const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
  AppState.tempData.columnBg = isDark ? '#374151' : '#edf2f7';
  AppState.tempData.columnText = isDark ? '#e2e8f0' : '#1a202c';
  renderColorPicker('columnBgColors', COLOR_PALETTE, AppState.tempData.columnBg, 'setColumnBg');
  renderColorPicker('columnTextColors', TEXT_COLORS, AppState.tempData.columnText, 'setColumnText');
  document.getElementById('columnBgCustom').value = AppState.tempData.columnBg;
  document.getElementById('columnTextCustom').value = AppState.tempData.columnText;
  openModal('columnModal');
  document.getElementById('columnTitle').focus();
}

function setColumnBg(c) { AppState.tempData.columnBg = c; document.getElementById('columnBgCustom').value = c; }
function setColumnText(c) { AppState.tempData.columnText = c; document.getElementById('columnTextCustom').value = c; }

function openEditColumnModal(colId) {
  const board = getActiveBoard();
  const col = board.columns.find(c => c.id === colId);
  if (!col) return;
  AppState.editing.columnId = colId;
  document.getElementById('columnModalTitle').textContent = 'ویرایش ستون';
  document.getElementById('columnTitle').value = col.title;
  document.getElementById('columnTitleError').style.display = 'none';
  const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
  AppState.tempData.columnBg = col.backgroundColor || (isDark ? '#374151' : '#edf2f7');
  AppState.tempData.columnText = col.textColor || (isDark ? '#e2e8f0' : '#1a202c');
  renderColorPicker('columnBgColors', COLOR_PALETTE, AppState.tempData.columnBg, 'setColumnBg');
  renderColorPicker('columnTextColors', TEXT_COLORS, AppState.tempData.columnText, 'setColumnText');
  document.getElementById('columnBgCustom').value = AppState.tempData.columnBg;
  document.getElementById('columnTextCustom').value = AppState.tempData.columnText;
  openModal('columnModal');
}

function validateColumnTitle(title, excludeId = null) {
  if (!title.trim()) return 'عنوان ستون الزامی است';
  const board = getActiveBoard();
  if (!board) return null;
  const exists = board.columns.some(c => 
    c.title.trim().toLowerCase() === title.trim().toLowerCase() && 
    c.id !== excludeId
  );
  if (exists) return 'ستونی با این عنوان وجود دارد';
  return null;
}

function saveColumn() {
  const title = document.getElementById('columnTitle').value.trim();
  const error = validateColumnTitle(title, AppState.editing.columnId);
  if (error) {
    const errorEl = document.getElementById('columnTitleError');
    errorEl.textContent = error;
    errorEl.style.display = 'block';
    return;
  }
  
  const board = getActiveBoard();
  if (!board) return;

  if (AppState.editing.columnId) {
    const col = board.columns.find(c => c.id === AppState.editing.columnId);
    const oldData = {
      title: col.title,
      backgroundColor: col.backgroundColor,
      textColor: col.textColor
    };
    const newData = {
      title,
      backgroundColor: AppState.tempData.columnBg,
      textColor: AppState.tempData.columnText
    };
    const cmd = new UpdateColumnCommand(board.id, AppState.editing.columnId, oldData, newData);
    CommandHistory.execute(cmd);
    showToast('ستون بروزرسانی شد', 'success');
  } else {
    const cmd = new CreateColumnCommand(board.id, {
      title,
      backgroundColor: AppState.tempData.columnBg,
      textColor: AppState.tempData.columnText
    });
    CommandHistory.execute(cmd);
    showToast('ستون ایجاد شد', 'success');
  }
  closeModal('columnModal');
  renderBoard();
}

function deleteColumn(colId) {
  if (!confirm('آیا از حذف این ستون و تمام کارت‌های آن مطمئن هستید؟')) return;
  const board = getActiveBoard();
  if (!board) return;
  const cmd = new DeleteColumnCommand(board.id, colId);
  CommandHistory.execute(cmd);
  renderBoard();
  showToast('ستون حذف شد', 'success');
}

// ============ Card Management ============
function openNewCardModal(columnId) {
  AppState.editing.cardId = null;
  AppState.editing.columnId = columnId;
  document.getElementById('cardModalTitle').textContent = 'ایجاد کارت جدید';
  document.getElementById('cardTitle').value = '';
  document.getElementById('cardDesc').value = '';
  document.getElementById('cardDate').value = '';
  document.getElementById('deleteCardBtn').style.display = 'none';
  AppState.tempData.card = {
    assignees: [],
    tags: [],
    dependencies: [],
    checklist: [],
    history: [{ from: '—', to: findColumnById(columnId)?.title || '—', date: new Date().toISOString() }],
    backgroundColor: null,
    textColor: null
  };
  renderColorPicker('cardBgColors', COLOR_PALETTE, null, 'setCardBg');
  document.getElementById('cardBgCustom').value = '#ffffff';
  renderCardEditor();
  openModal('cardModal');
  document.getElementById('cardTitle').focus();
}

function setCardBg(c) {
  if (!AppState.tempData.card) AppState.tempData.card = {};
  AppState.tempData.card.backgroundColor = c;
  document.getElementById('cardBgCustom').value = c;
}

function openEditCardModal(cardId) {
  const { card, column } = findCard(cardId);
  if (!card) return;
  AppState.editing.cardId = cardId;
  AppState.editing.columnId = column.id;
  document.getElementById('cardModalTitle').textContent = 'ویرایش کارت';
  document.getElementById('cardTitle').value = card.title;
  document.getElementById('cardDesc').value = card.description || '';
  if (card.estimatedDate) {
    const d = new Date(card.estimatedDate);
    const local = new Date(d.getTime() - d.getTimezoneOffset()*60000).toISOString().slice(0,16);
    document.getElementById('cardDate').value = local;
  } else {
    document.getElementById('cardDate').value = '';
  }
  document.getElementById('deleteCardBtn').style.display = 'inline-flex';
  AppState.tempData.card = {
    assignees: [...(card.assignees || [])],
    tags: [...(card.tags || [])],
    dependencies: [...(card.dependencies || [])],
    checklist: (card.checklist || []).map(i => ({...i})),
    history: [...(card.history || [])],
    backgroundColor: card.backgroundColor || null,
    textColor: card.textColor || null
  };
  renderColorPicker('cardBgColors', COLOR_PALETTE, AppState.tempData.card.backgroundColor, 'setCardBg');
  document.getElementById('cardBgCustom').value = AppState.tempData.card.backgroundColor || '#ffffff';
  renderCardEditor();
  openModal('cardModal');
}

function renderCardEditor() {
  renderAssignees();
  renderCardTags();
  renderDependencies();
  renderChecklist();
  renderHistory();
  renderDependencySelect();
}

function renderAssignees() {
  const container = document.getElementById('assigneesContainer');
  const list = AppState.tempData.card.assignees;
  if (list.length === 0) {
    container.innerHTML = '<div class="empty-state" style="padding:10px;"><div style="font-size:12px;">هنوز کسی اضافه نشده</div></div>';
    return;
  }
  container.innerHTML = list.map((a, idx) => `
    <div class="assignee-item">
      <div class="assignee-avatar-lg">${escapeHtml((a.name||'?').charAt(0).toUpperCase())}</div>
      <div class="assignee-name">${escapeHtml(a.name)}</div>
      <button class="btn btn-sm btn-danger" data-action="remove-assignee" data-idx="${idx}" aria-label="حذف ${escapeHtml(a.name)}">✕</button>
    </div>
  `).join('');
}

function addAssignee() {
  const input = document.getElementById('newAssigneeInput');
  const name = input.value.trim();
  if (!name) return;
  AppState.tempData.card.assignees.push({ id: generateId(), name });
  input.value = '';
  renderAssignees();
}

function removeAssignee(idx) {
  AppState.tempData.card.assignees.splice(idx, 1);
  renderAssignees();
}

function renderCardTags() {
  const container = document.getElementById('cardTagsContainer');
  if (AppState.tags.length === 0) {
    container.innerHTML = '<div class="empty-state" style="padding:10px;width:100%;"><div style="font-size:12px;">تگی وجود ندارد. از منوی اصلی تگ ایجاد کنید.</div></div>';
    return;
  }
  container.innerHTML = AppState.tags.map(tag => {
    const selected = AppState.tempData.card.tags.includes(tag.id);
    return `<div class="tag-chip ${selected ? 'selected' : ''}" style="background:${tag.color};" data-action="toggle-tag" data-id="${tag.id}" role="button" tabindex="0" aria-pressed="${selected}">${escapeHtml(tag.name)}</div>`;
  }).join('');
}

function toggleCardTag(tagId) {
  const idx = AppState.tempData.card.tags.indexOf(tagId);
  if (idx >= 0) AppState.tempData.card.tags.splice(idx, 1);
  else AppState.tempData.card.tags.push(tagId);
  renderCardTags();
}

function renderDependencySelect() {
  const select = document.getElementById('depSelect');
  const board = getActiveBoard();
  if (!board) { select.innerHTML = '<option>بردی وجود ندارد</option>'; select.disabled = true; return; }
  const allCards = [];
  board.columns.forEach(col => {
    col.cards.forEach(card => {
      if (card.id !== AppState.editing.cardId) {
        allCards.push({ id: card.id, title: card.title, columnTitle: col.title });
      }
    });
  });
  if (allCards.length === 0) {
    select.innerHTML = '<option>کارت دیگری وجود ندارد</option>';
    select.disabled = true;
    return;
  }
  select.disabled = false;
  select.innerHTML = '<option value="">-- انتخاب کارت --</option>' +
    allCards.map(c => `<option value="${c.id}">${escapeHtml(c.title)} (${escapeHtml(c.columnTitle)})</option>`).join('');
}

function renderDependencies() {
  const container = document.getElementById('depList');
  const deps = AppState.tempData.card.dependencies;
  if (deps.length === 0) {
    container.innerHTML = '<div class="empty-state" style="padding:10px;"><div style="font-size:12px;">وابستگی انتخاب نشده</div></div>';
    return;
  }
  container.innerHTML = deps.map(depId => {
    const depCard = findCardById(depId);
    if (!depCard) return '';
    return `
      <div class="dep-item">
        <div class="dep-status"></div>
        <div class="dep-title">${escapeHtml(depCard.title)}</div>
        <button class="btn btn-sm btn-danger" data-action="remove-dependency" data-id="${depId}" aria-label="حذف وابستگی">✕</button>
      </div>
    `;
  }).join('');
}

function addDependency() {
  const select = document.getElementById('depSelect');
  const depId = select.value;
  if (!depId) { showToast('یک کارت انتخاب کنید', 'warning'); return; }
  if (AppState.tempData.card.dependencies.includes(depId)) {
    showToast('این وابستگی قبلاً اضافه شده', 'warning'); return;
  }
  AppState.tempData.card.dependencies.push(depId);
  renderDependencies();
}

function removeDependency(depId) {
  AppState.tempData.card.dependencies = AppState.tempData.card.dependencies.filter(d => d !== depId);
  renderDependencies();
}

function renderChecklist() {
  const container = document.getElementById('checklistContainer');
  const list = AppState.tempData.card.checklist;
  if (list.length === 0) {
    container.innerHTML = '<div class="empty-state" style="padding:10px;"><div style="font-size:12px;">آیتمی وجود ندارد</div></div>';
    return;
  }
  container.innerHTML = list.map((item, idx) => `
    <div class="checklist-item ${item.done ? 'done' : ''}">
      <input type="checkbox" ${item.done ? 'checked' : ''} data-action="toggle-checklist" data-idx="${idx}" aria-label="${escapeHtml(item.title)}">
      <div class="checklist-text">${escapeHtml(item.title)}</div>
      <button class="checklist-remove" data-action="remove-checklist" data-idx="${idx}" aria-label="حذف">✕</button>
    </div>
  `).join('');
}

function addChecklistItem() {
  const input = document.getElementById('newChecklistInput');
  const title = input.value.trim();
  if (!title) return;
  AppState.tempData.card.checklist.push({ id: generateId(), title, done: false });
  input.value = '';
  renderChecklist();
}

function toggleChecklistItem(idx) {
  AppState.tempData.card.checklist[idx].done = !AppState.tempData.card.checklist[idx].done;
  renderChecklist();
}

function removeChecklistItem(idx) {
  AppState.tempData.card.checklist.splice(idx, 1);
  renderChecklist();
}

function renderHistory() {
  const container = document.getElementById('historyList');
  const history = AppState.tempData.card.history;
  if (history.length === 0) {
    container.innerHTML = '<div class="empty-state" style="padding:10px;"><div style="font-size:12px;">هنوز تاریخچه‌ای ثبت نشده</div></div>';
    return;
  }
  container.innerHTML = history.slice().reverse().map(h => `
    <div class="history-item">
      <div class="history-icon">${h.action === 'reorder' ? '↕' : '→'}</div>
      <div class="history-content">
        <div>از <strong>${escapeHtml(h.from || '—')}</strong> به <strong>${escapeHtml(h.to || '—')}</strong>${h.action === 'reorder' ? ' <em>(تغییر ترتیب)</em>' : ''}</div>
        <div class="history-date">${formatDate(h.date)}</div>
      </div>
    </div>
  `).join('');
}

function saveCard() {
  const title = document.getElementById('cardTitle').value.trim();
  const desc = document.getElementById('cardDesc').value.trim();
  const dateVal = document.getElementById('cardDate').value;

  if (!title) { showToast('عنوان کارت الزامی است', 'error'); return; }
  if (!desc) { showToast('توضیحات الزامی است', 'error'); return; }

  const board = getActiveBoard();

  if (AppState.editing.cardId) {
    const { card } = findCard(AppState.editing.cardId);
    const oldData = {
      title: card.title,
      description: card.description,
      estimatedDate: card.estimatedDate,
      assignees: card.assignees,
      tags: card.tags,
      dependencies: card.dependencies,
      checklist: card.checklist,
      backgroundColor: card.backgroundColor,
      textColor: card.textColor
    };
    const newData = {
      title,
      description: desc,
      estimatedDate: dateVal ? new Date(dateVal).toISOString() : null,
      assignees: AppState.tempData.card.assignees,
      tags: AppState.tempData.card.tags,
      dependencies: AppState.tempData.card.dependencies,
      checklist: AppState.tempData.card.checklist,
      backgroundColor: AppState.tempData.card.backgroundColor,
      textColor: AppState.tempData.card.textColor
    };
    const cmd = new UpdateCardCommand(AppState.editing.cardId, oldData, newData);
    CommandHistory.execute(cmd);
    showToast('کارت بروزرسانی شد', 'success');
  } else {
    const cmd = new CreateCardCommand(AppState.editing.columnId, {
      title,
      description: desc,
      estimatedDate: dateVal ? new Date(dateVal).toISOString() : null,
      assignees: AppState.tempData.card.assignees,
      tags: AppState.tempData.card.tags,
      dependencies: AppState.tempData.card.dependencies,
      checklist: AppState.tempData.card.checklist,
      history: AppState.tempData.card.history,
      backgroundColor: AppState.tempData.card.backgroundColor,
      textColor: AppState.tempData.card.textColor
    });
    CommandHistory.execute(cmd);
    showToast('کارت ایجاد شد', 'success');
  }

  closeModal('cardModal');
  renderBoard();
}

function deleteCard() {
  if (!confirm('آیا از حذف این کارت مطمئن هستید؟')) return;
  const cmd = new DeleteCardCommand(AppState.editing.cardId);
  CommandHistory.execute(cmd);
  closeModal('cardModal');
  renderBoard();
  showToast('کارت حذف شد', 'success');
}

// ============ Helpers ============
function getActiveBoard() {
  return AppState.project.boards.find(b => b.id === AppState.activeBoardId);
}

function findColumnById(colId) {
  const board = getActiveBoard();
  if (!board) return null;
  return board.columns.find(c => c.id === colId);
}

function findCard(cardId) {
  const board = getActiveBoard();
  if (!board) return { card: null, column: null, board: null };
  for (const col of board.columns) {
    const card = col.cards.find(c => c.id === cardId);
    if (card) return { card, column: col, board };
  }
  return { card: null, column: null, board };
}

function findCardById(cardId) {
  const { card } = findCard(cardId);
  return card;
}

function findCardContext(cardId, columnId) {
  const board = getActiveBoard();
  if (!board) return { card: null, column: null, board: null };
  
  if (columnId) {
    const column = board.columns.find(c => c.id === columnId);
    if (column) {
      const card = cardId ? column.cards.find(c => c.id === cardId) : null;
      return { card, column, board };
    }
  }
  
  if (cardId) {
    for (const col of board.columns) {
      const card = col.cards.find(c => c.id === cardId);
      if (card) return { card, column: col, board };
    }
  }
  
  return { card: null, column: null, board: null };
}

// ============ Drag & Drop: Columns ============
let draggedColumnId = null;

function attachColumnDragEvents() {
  document.querySelectorAll('[data-col-drag]').forEach(header => {
    const colId = header.getAttribute('data-col-drag');
    const column = header.closest('.column');

    header.addEventListener('dragstart', (e) => {
      draggedColumnId = colId;
      e.dataTransfer.effectAllowed = 'move';
      e.dataTransfer.setData('text/plain', 'column');
      setTimeout(() => column.classList.add('dragging-col'), 0);
    });

    header.addEventListener('dragend', () => {
      column.classList.remove('dragging-col');
      draggedColumnId = null;
      document.querySelectorAll('.column').forEach(c => c.classList.remove('drag-over'));
    });
  });

  document.querySelectorAll('.column').forEach(col => {
    col.addEventListener('dragover', (e) => {
      if (!draggedColumnId || draggedColumnId === col.dataset.columnId) return;
      e.preventDefault();
      col.classList.add('drag-over');
    });

    col.addEventListener('dragleave', (e) => {
      if (!col.contains(e.relatedTarget)) col.classList.remove('drag-over');
    });

    col.addEventListener('drop', (e) => {
      e.preventDefault();
      col.classList.remove('drag-over');
      if (!draggedColumnId || draggedColumnId === col.dataset.columnId) return;
      const board = getActiveBoard();
      const fromIdx = board.columns.findIndex(c => c.id === draggedColumnId);
      const toIdx = board.columns.findIndex(c => c.id === col.dataset.columnId);
      if (fromIdx < 0 || toIdx < 0) return;
      
      const cmd = new ReorderColumnsCommand(board.id, fromIdx, toIdx);
      CommandHistory.execute(cmd);
      draggedColumnId = null;
      renderBoard();
      showToast('ترتیب ستون‌ها تغییر کرد', 'success');
    });
  });
}

// ============ Drag & Drop: Cards ============
let draggedCardId = null;
let draggedFromColumnId = null;
let draggedFromIndex = -1;
let isDragging = false;

function attachCardDragEvents() {
  document.querySelectorAll('.card').forEach(cardEl => {
    cardEl.addEventListener('dragstart', (e) => {
      isDragging = true;
      draggedCardId = cardEl.dataset.cardId;
      const colBody = cardEl.closest('.column-body');
      draggedFromColumnId = colBody.dataset.columnId;
      const col = findColumnById(draggedFromColumnId);
      draggedFromIndex = col ? col.cards.findIndex(c => c.id === draggedCardId) : -1;
      e.dataTransfer.effectAllowed = 'move';
      e.dataTransfer.setData('text/plain', 'card');
      e.stopPropagation();
      setTimeout(() => cardEl.classList.add('dragging'), 0);
    });

    cardEl.addEventListener('dragend', () => {
      cardEl.classList.remove('dragging');
      setTimeout(() => { isDragging = false; }, 100);
      draggedCardId = null;
      draggedFromColumnId = null;
      draggedFromIndex = -1;
      document.querySelectorAll('.column-body').forEach(b => b.classList.remove('drag-target'));
    });
  });

  document.querySelectorAll('.column-body').forEach(body => {
    body.addEventListener('dragover', (e) => {
      if (!draggedCardId) return;
      e.preventDefault();
      body.classList.add('drag-target');
    });

    body.addEventListener('dragleave', (e) => {
      if (e.target === body) body.classList.remove('drag-target');
    });

    body.addEventListener('drop', (e) => {
      e.preventDefault();
      body.classList.remove('drag-target');
      if (!draggedCardId || isDragging === false) return;
      const targetColumnId = body.dataset.columnId;
      const board = getActiveBoard();
      if (!board) return;

      const afterElement = getDragAfterElement(body, e.clientY);
      const toCol = board.columns.find(c => c.id === targetColumnId);
      let toIndex;
      
      if (afterElement == null) {
        toIndex = toCol.cards.length;
      } else {
        toIndex = toCol.cards.findIndex(c => c.id === afterElement.dataset.cardId);
      }
      
      if (draggedFromColumnId === targetColumnId && draggedFromIndex < toIndex) {
        toIndex--;
      }
      
      if (toIndex < 0) toIndex = 0;
      
      if (draggedFromColumnId === targetColumnId && draggedFromIndex === toIndex) {
        return;
      }

      const cmd = new MoveCardCommand(
        draggedCardId,
        draggedFromColumnId,
        targetColumnId,
        draggedFromIndex,
        toIndex
      );
      CommandHistory.execute(cmd);
      
      const toColName = toCol.title;
      
      draggedCardId = null;
      draggedFromColumnId = null;
      draggedFromIndex = -1;
      
      renderBoard();
      showToast(`کارت به "${toColName}" منتقل شد`, 'success');
    });
  });
}

function getDragAfterElement(container, y) {
  const cards = [...container.querySelectorAll('.card:not(.dragging)')];
  return cards.reduce((closest, child) => {
    const box = child.getBoundingClientRect();
    const offset = y - box.top - box.height / 2;
    if (offset < 0 && offset > closest.offset) {
      return { offset, element: child };
    }
    return closest;
  }, { offset: Number.NEGATIVE_INFINITY }).element;
}

// ============ Event Delegation ============
document.addEventListener('click', (e) => {
  const target = e.target.closest('[data-action]');
  if (!target) return;
  
  const action = target.dataset.action;
  const id = target.dataset.id;
  
  if (action === 'delete-board' && target.classList.contains('tab-close')) {
    e.stopPropagation();
    deleteBoard(id);
    return;
  }
  
  switch (action) {
    case 'switch-board':
      if (!e.target.closest('.tab-close')) switchBoard(id);
      break;
    case 'delete-board': deleteBoard(id); break;
    case 'new-column': openNewColumnModal(); break;
    case 'edit-column': openEditColumnModal(id); break;
    case 'delete-column': deleteColumn(id); break;
    case 'new-card': openNewCardModal(target.dataset.columnId); break;
    case 'edit-card':
      if (!isDragging) openEditCardModal(id);
      break;
    case 'select-color':
      selectColor(target.dataset.container, target.dataset.color, target.dataset.callback);
      break;
    case 'toggle-tag': toggleCardTag(id); break;
    case 'remove-assignee': removeAssignee(parseInt(target.dataset.idx)); break;
    case 'remove-dependency': removeDependency(id); break;
    case 'remove-checklist': removeChecklistItem(parseInt(target.dataset.idx)); break;
    case 'delete-tag': deleteTag(id); break;
  }
});

document.addEventListener('change', (e) => {
  const target = e.target;
  if (target.matches('[data-action="toggle-checklist"]')) {
    toggleChecklistItem(parseInt(target.dataset.idx));
  }
});

// ============ Save / Load ============
function saveProject() {
  document.getElementById('saveFileName').value = `${AppState.project.title.replace(/[^\w\u0600-\u06FF]/g, '_') || 'xkanban'}.json`;
  openModal('saveModal');
}

function downloadProject() {
  const fileName = document.getElementById('saveFileName').value.trim() || 'xkanban-project.json';
  const data = {
    version: APP_VERSION,
    project: AppState.project,
    tags: AppState.tags,
    activeBoardId: AppState.activeBoardId,
    exportedAt: new Date().toISOString(),
    author: 'Hadi Khazaee Asl'
  };
  const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = fileName;
  a.click();
  URL.revokeObjectURL(url);
  closeModal('saveModal');
  showToast('پروژه با موفقیت ذخیره شد', 'success');
  
  WebhookManager.send(EVENT_TYPES.PROJECT_SAVED, {
    fileName: fileName,
    boardsCount: AppState.project.boards.length,
    tagsCount: AppState.tags.length
  }, { triggeredBy: 'manual-save' });
}

function loadProject() {
  document.getElementById('fileInput').click();
}

document.getElementById('fileInput').addEventListener('change', (e) => {
  const file = e.target.files[0];
  if (!file) return;
  const reader = new FileReader();
  reader.onload = async (evt) => {
    try {
      let data = JSON.parse(evt.target.result);
      if (!data.project || !Array.isArray(data.project.boards)) {
        throw new Error('ساختار فایل نامعتبر است');
      }
      
      data = MigrationManager.migrate(data);
      
      AppState.project = data.project;
      AppState.tags = data.tags || [];
      AppState.activeBoardId = data.activeBoardId || AppState.project.boards[0]?.id || null;
      CommandHistory.clear();
      renderTabs();
      renderBoard();
      showToast('پروژه با موفقیت بارگذاری شد', 'success');
      AutoSaveManager.scheduleSave();
      
      await WebhookManager.send(EVENT_TYPES.PROJECT_LOADED, {
        fileName: file.name,
        fileSize: file.size,
        boardsCount: AppState.project.boards.length,
        tagsCount: AppState.tags.length
      }, { triggeredBy: 'file-load' });
    } catch (err) {
      showToast('خطا در بارگذاری فایل: ' + err.message, 'error');
    }
  };
  reader.readAsText(file);
  e.target.value = '';
});

// ============ Export ============
function openExportModal() {
  const select = document.getElementById('exportBoardSelect');
  select.innerHTML = '<option value="all">همه بردها</option>';
  AppState.project.boards.forEach(board => {
    const opt = document.createElement('option');
    opt.value = board.id;
    opt.textContent = board.title;
    select.appendChild(opt);
  });
  openModal('exportModal');
}

function exportProject() {
  const boardId = document.getElementById('exportBoardSelect').value;
  const format = document.getElementById('exportFormat').value;
  
  const boardName = boardId === 'all' 
    ? AppState.project.title 
    : AppState.project.boards.find(b => b.id === boardId)?.title || 'board';
  
  const safeName = boardName.replace(/[^\w\u0600-\u06FF]/g, '_');
  
  if (format === 'markdown') {
    const content = ExportManager.toMarkdown(boardId);
    ExportManager.download(content, `${safeName}.md`, 'text/markdown');
  } else if (format === 'csv') {
    const content = '\uFEFF' + ExportManager.toCSV(boardId);
    ExportManager.download(content, `${safeName}.csv`, 'text/csv');
  }
  
  closeModal('exportModal');
  showToast('خروجی با موفقیت ایجاد شد', 'success');
}

// ============ Auto-save Recovery ============
function checkAutosaveRecovery() {
  const saved = AutoSaveManager.load();
  if (!saved) return;
  
  document.getElementById('autosaveDate').textContent = formatDate(saved.savedAt);
  openModal('autosaveModal');
}

function recoverAutosave() {
  const saved = AutoSaveManager.load();
  if (!saved) {
    closeModal('autosaveModal');
    return;
  }
  
  try {
    const data = MigrationManager.migrate(saved);
    AppState.project = data.project;
    AppState.tags = data.tags || [];
    AppState.activeBoardId = data.activeBoardId || AppState.project.boards[0]?.id || null;
    CommandHistory.clear();
    renderTabs();
    renderBoard();
    showToast('داده‌ها با موفقیت بازیابی شدند', 'success');
  } catch (err) {
    showToast('خطا در بازیابی: ' + err.message, 'error');
  }
  
  closeModal('autosaveModal');
}

function discardAutosave() {
  AutoSaveManager.clear();
  closeModal('autosaveModal');
  showToast('ذخیره خودکار نادیده گرفته شد', 'info');
}

// ============ DateTime Footer ============
function updateDate() {
  const now = new Date();
  document.getElementById('currentDate').textContent = now.toLocaleDateString('fa-IR');
}
setInterval(updateDate, 60000);
updateDate();

// ============ Keyboard Shortcuts ============
document.addEventListener('keydown', (e) => {
  if (e.ctrlKey && e.key === 's') {
    e.preventDefault();
    saveProject();
    return;
  }
  if (e.ctrlKey && e.key === 'z') {
    e.preventDefault();
    CommandHistory.undo();
    return;
  }
  if (e.ctrlKey && e.key === 'y') {
    e.preventDefault();
    CommandHistory.redo();
    return;
  }
  if (e.ctrlKey && e.key === 'f') {
    e.preventDefault();
    document.getElementById('searchInput').focus();
    return;
  }
  if (e.key === 'Escape') {
    document.querySelectorAll('.modal-overlay.active').forEach(m => m.classList.remove('active'));
    KeyboardManager.clearFocus();
    return;
  }
  
  KeyboardManager.handleKey(e);
});

// ============ Search/Filter Events ============
document.getElementById('searchInput').addEventListener('input', () => SearchManager.apply());
document.getElementById('filterTag').addEventListener('change', () => SearchManager.apply());
document.getElementById('filterAssignee').addEventListener('change', () => SearchManager.apply());
document.getElementById('filterStatus').addEventListener('change', () => SearchManager.apply());

// ============ Initialize ============
function init() {
  initTheme();
  updateSidebarToggleIcon();
  
  // Load webhook settings from storage
  WebhookManager.loadFromStorage();
  
  renderTabs();
  renderBoard();
  
  setTimeout(() => {
    checkAutosaveRecovery();
  }, 500);
  
  AutoSaveManager.scheduleSave();
  
  window.addEventListener('beforeunload', () => {
    AutoSaveManager.save();
  });
  
  showToast('به xKanban خوش آمدید', 'success');
}

init();
</script>

</body>
</html>

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Resources\Tools\xToolTemplate.html

<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>پروژه نمونه</title>
  <style>
    @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap');
    /* ============ Theme Variables ============ */
    :root {
      --primary: #1a365d;
      --primary-light: #2c5282;
      --primary-dark: #0f2341;
      --accent: #c9a961;
      --accent-dark: #a8893f;
      --accent-light: #e0c789;
      --bg: #f5f7fa;
      --board-bg: #e2e8f0;
      --card-bg: #ffffff;
      --column-bg: #edf2f7;
      --panel-bg: #ffffff;
      --text: #1a202c;
      --text-muted: #4a5568;
      --text-light: #718096;
      --border: #cbd5e0;
      --border-light: #e2e8f0;
      --success: #38a169;
      --danger: #e53e3e;
      --warning: #dd6b20;
      --info: #3182ce;
      --shadow: 0 4px 6px rgba(0,0,0,0.08), 0 10px 20px rgba(26,54,93,0.1);
      --shadow-sm: 0 2px 4px rgba(0,0,0,0.06);
      --shadow-lg: 0 10px 30px rgba(0,0,0,0.15);
      --radius: 10px;
      --radius-sm: 6px;
      --sidebar-width: 280px;
      --header-height: 64px;
      --tabs-height: 48px;
      --footer-height: 36px;
    }
    [data-theme="dark"] {
      --primary: #2c5282;
      --primary-light: #3b6fb5;
      --primary-dark: #1a365d;
      --accent: #e0c789;
      --accent-dark: #c9a961;
      --accent-light: #f0d9a0;
      --bg: #1a202c;
      --board-bg: #2d3748;
      --card-bg: #2d3748;
      --column-bg: #374151;
      --panel-bg: #1f2937;
      --text: #e2e8f0;
      --text-muted: #cbd5e0;
      --text-light: #a0aec0;
      --border: #4a5568;
      --border-light: #374151;
      --shadow: 0 4px 6px rgba(0,0,0,0.3), 0 10px 20px rgba(0,0,0,0.4);
      --shadow-sm: 0 2px 4px rgba(0,0,0,0.2);
      --shadow-lg: 0 10px 30px rgba(0,0,0,0.5);
    }
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body {
      font-family: 'Vazirmatn', Tahoma, sans-serif;
      background: var(--bg);
      color: var(--text);
      height: 100vh;
      overflow: hidden;
      font-size: 14px;
      display: flex;
      flex-direction: column;
      transition: background 0.3s, color 0.3s;
    }
    /* ============ Header ============ */
    .app-header {
      background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);
      color: white;
      padding: 0 20px;
      height: var(--header-height);
      display: flex;
      align-items: center;
      justify-content: space-between;
      border-bottom: 3px solid var(--accent);
      box-shadow: var(--shadow);
      z-index: 100;
      flex-shrink: 0;
      position: relative;
      overflow: hidden;
    }
    .app-header::before {
      content: '';
      position: absolute;
      top: -50%;
      left: -10%;
      width: 300px;
      height: 300px;
      background: radial-gradient(circle, rgba(201,169,97,0.15) 0%, transparent 70%);
      border-radius: 50%;
      pointer-events: none;
    }
    .app-brand {
      display: flex;
      align-items: center;
      gap: 14px;
      position: relative;
      z-index: 1;
    }
    .app-logo {
      width: 44px; height: 44px;
      background: var(--accent);
      border-radius: 10px;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 22px;
      font-weight: 800;
      color: var(--primary-dark);
      box-shadow: 0 4px 12px rgba(0,0,0,0.25);
      flex-shrink: 0;
    }
    .app-brand-text {
      display: flex;
      flex-direction: column;
      gap: 2px;
    }
    .app-title {
      font-size: 16px;
      font-weight: 800;
      letter-spacing: -0.3px;
      color: white;
    }
    .app-subtitle {
      font-size: 11px;
      opacity: 0.85;
      color: rgba(255,255,255,0.9);
    }
    /* ============ Buttons ============ */
    .btn {
      background: linear-gradient(135deg, var(--primary-light), var(--primary));
      color: white;
      border: none;
      padding: 8px 14px;
      border-radius: var(--radius-sm);
      cursor: pointer;
      font-family: inherit;
      font-size: 13px;
      font-weight: 600;
      display: inline-flex;
      align-items: center;
      gap: 6px;
      transition: all 0.2s;
      box-shadow: 0 2px 4px rgba(0,0,0,0.15);
      white-space: nowrap;
    }
    .btn:hover { transform: translateY(-1px); box-shadow: 0 4px 8px rgba(0,0,0,0.2); filter: brightness(1.1); }
    .btn:active { transform: translateY(0); }
    .btn-accent {
      background: linear-gradient(135deg, var(--accent), var(--accent-dark));
      color: var(--primary-dark);
      font-weight: 700;
    }
    .btn-ghost {
      background: rgba(26,54,93,0.08);
      border: 1px solid var(--border);
      color: var(--primary);
      font-weight: 600;
    }
    .btn-ghost:hover {
      background: rgba(26,54,93,0.15);
      border-color: var(--primary-light);
    }
    [data-theme="dark"] .btn-ghost {
      background: rgba(255,255,255,0.1);
      border: 1px solid rgba(255,255,255,0.25);
      color: white;
    }
    [data-theme="dark"] .btn-ghost:hover {
      background: rgba(255,255,255,0.2);
    }
    /* ============ Main Layout ============ */
    .app-main {
      flex: 1;
      display: flex;
      overflow: hidden;
      position: relative;
      flex-direction: row;
    }
    /* ============ Sidebar ============ */
    .sidebar {
      order: -1;
      width: var(--sidebar-width);
      background: var(--panel-bg);
      border-right: 1px solid var(--border);
      display: flex;
      flex-direction: column;
      transition: width 0.3s ease;
      box-shadow: var(--shadow);
      overflow: hidden;
      flex-shrink: 0;
      z-index: 50;
    }
    .sidebar.collapsed {
      width: 0;
      overflow: hidden;
    }
    .sidebar-header {
      background: linear-gradient(135deg, var(--primary), var(--primary-light));
      color: white;
      padding: 12px;
      display: flex;
      align-items: center;
      justify-content: space-between;
      border-bottom: 2px solid var(--accent);
      min-height: 48px;
    }
    .sidebar-title {
      font-size: 13px;
      font-weight: 700;
      display: flex;
      align-items: center;
      gap: 8px;
      white-space: nowrap;
      overflow: hidden;
    }
    .sidebar.collapsed .sidebar-title { display: none; }
    .sidebar-body {
      flex: 1;
      overflow-y: auto;
      padding: 8px;
    }
    .sidebar.collapsed .sidebar-body { display: none; }
    .sidebar-group {
      margin-bottom: 6px;
      border: 1px solid var(--border-light);
      border-radius: var(--radius-sm);
      overflow: hidden;
      background: var(--card-bg);
    }
    .sidebar-group-header {
      background: linear-gradient(135deg, var(--primary), var(--primary-light));
      color: white;
      padding: 10px 12px;
      cursor: pointer;
      display: flex;
      align-items: center;
      justify-content: space-between;
      font-size: 13px;
      font-weight: 700;
      user-select: none;
      transition: all 0.2s;
      position: relative;
    }
    .sidebar-group-header:hover { filter: brightness(1.1); }
    .sidebar-group-header .group-icon {
      display: flex;
      align-items: center;
      gap: 8px;
      flex: 1;
      overflow: hidden;
    }
    .sidebar-group-header .group-arrow {
      transition: transform 0.3s;
      font-size: 12px;
      flex-shrink: 0;
    }
    .sidebar-group.collapsed .group-arrow { transform: rotate(90deg); }
    .sidebar-group.collapsed .sidebar-group-body { display: none; }
    .sidebar-group-body {
      padding: 6px;
      display: flex;
      flex-direction: column;
      gap: 4px;
    }
    .sidebar-btn {
      background: var(--card-bg);
      border: 1px solid var(--border-light);
      color: var(--text);
      padding: 8px 10px;
      border-radius: var(--radius-sm);
      cursor: pointer;
      font-family: inherit;
      font-size: 12px;
      font-weight: 500;
      display: flex;
      align-items: center;
      gap: 8px;
      transition: all 0.2s;
      text-align: right;
      width: 100%;
      position: relative;
    }
    .sidebar-btn:hover {
      background: linear-gradient(135deg, var(--primary), var(--primary-light));
      color: white;
      border-color: var(--primary);
      transform: translateX(-2px);
    }
    .sidebar-btn .btn-icon-emoji {
      font-size: 15px;
      width: 20px;
      text-align: center;
      flex-shrink: 0;
    }
    .sidebar-btn .btn-title {
      flex: 1;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
    .sidebar-btn .shortcut-badge {
      font-size: 9px;
      font-weight: 700;
      padding: 2px 6px;
      background: rgba(0,0,0,0.08);
      color: var(--text-muted);
      border-radius: 4px;
      font-family: 'Vazirmatn', monospace;
      flex-shrink: 0;
      letter-spacing: 0.5px;
    }
    .sidebar-btn:hover .shortcut-badge {
      background: rgba(255,255,255,0.2);
      color: white;
    }
    /* ============ Tooltip ============ */
    [data-tooltip] {
      position: relative;
    }
    [data-tooltip]::after {
      content: attr(data-tooltip);
      position: absolute;
      right: calc(100% + 10px);
      top: 50%;
      transform: translateY(-50%);
      background: var(--primary-dark);
      color: white;
      padding: 8px 12px;
      border-radius: 6px;
      font-size: 11px;
      font-weight: 500;
      white-space: nowrap;
      opacity: 0;
      pointer-events: none;
      transition: opacity 0.2s;
      z-index: 1000;
      box-shadow: 0 4px 12px rgba(0,0,0,0.3);
      max-width: 250px;
      white-space: normal;
      line-height: 1.5;
    }
    [data-tooltip]::before {
      content: '';
      position: absolute;
      right: calc(100% + 5px);
      top: 50%;
      transform: translateY(-50%);
      border: 5px solid transparent;
      border-left-color: var(--primary-dark);
      opacity: 0;
      pointer-events: none;
      transition: opacity 0.2s;
      z-index: 1000;
    }
    [data-tooltip]:hover::after,
    [data-tooltip]:hover::before {
      opacity: 1;
    }
    [data-theme="dark"] [data-tooltip]::after {
      background: var(--accent-dark);
      color: var(--primary-dark);
    }
    [data-theme="dark"] [data-tooltip]::before {
      border-left-color: var(--accent-dark);
    }
    /* ============ Tabs Bar ============ */
    .tabs-bar {
      background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary) 100%);
      border-bottom: 2px solid var(--accent);
      display: flex;
      align-items: center;
      padding: 0 10px;
      min-height: var(--tabs-height);
      box-shadow: var(--shadow-sm);
      flex-shrink: 0;
      gap: 6px;
      position: relative;
    }
    [data-theme="dark"] .tabs-bar {
      background: linear-gradient(135deg, #0f1724 0%, #1a2a44 100%);
    }
    .tab-control-btn {
      background: rgba(255,255,255,0.1);
      border: 1px solid rgba(255,255,255,0.15);
      color: white;
      width: 32px; height: 32px;
      border-radius: 6px;
      cursor: pointer;
      font-size: 14px;
      display: flex;
      align-items: center;
      justify-content: center;
      transition: all 0.2s;
      padding: 0;
    }
    .tab-control-btn:hover:not(:disabled) {
      background: var(--accent);
      color: var(--primary-dark);
      border-color: var(--accent);
      transform: translateY(-1px);
    }
    /* ============ Board Area ============ */
    .board-area {
      flex: 1;
      overflow: auto;
      padding: 20px;
      background: var(--board-bg);
      transition: background 0.3s;
    }
    .board-empty {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      height: 100%;
      color: var(--text-muted);
      text-align: center;
    }
    .board-empty-icon { font-size: 80px; opacity: 0.3; margin-bottom: 20px; }
    .board-empty h2 { font-size: 20px; color: var(--primary); margin-bottom: 10px; }
    [data-theme="dark"] .board-empty h2 { color: var(--accent); }
    .board-empty p { margin-bottom: 20px; }
    /* ============ Modals ============ */
    .modal-overlay {
      position: fixed;
      inset: 0;
      background: rgba(26,54,93,0.6);
      backdrop-filter: blur(3px);
      display: none;
      align-items: center;
      justify-content: center;
      z-index: 1000;
      padding: 20px;
      animation: fadeIn 0.2s;
    }
    .modal-overlay.active { display: flex; }
    @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
    .modal {
      background: var(--panel-bg);
      color: var(--text);
      border-radius: 12px;
      width: 100%;
      max-width: 600px;
      max-height: 90vh;
      overflow: hidden;
      display: flex;
      flex-direction: column;
      box-shadow: var(--shadow-lg);
      animation: slideUp 0.25s;
    }
    .modal-lg { max-width: 800px; }
    @keyframes slideUp {
      from { opacity: 0; transform: translateY(20px); }
      to { opacity: 1; transform: translateY(0); }
    }
    .modal-header {
      background: linear-gradient(135deg, var(--primary), var(--primary-light));
      color: white;
      padding: 16px 20px;
      display: flex;
      align-items: center;
      justify-content: space-between;
      border-bottom: 3px solid var(--accent);
    }
    .modal-title {
      font-size: 16px;
      font-weight: 700;
      display: flex;
      align-items: center;
      gap: 10px;
    }
    .modal-close {
      background: rgba(255,255,255,0.15);
      border: none;
      color: white;
      width: 30px; height: 30px;
      border-radius: 50%;
      cursor: pointer;
      font-size: 16px;
      transition: all 0.2s;
    }
    .modal-close:hover { background: var(--danger); }
    .modal-body {
      padding: 20px;
      overflow-y: auto;
      flex: 1;
    }
    .modal-footer {
      padding: 14px 20px;
      border-top: 1px solid var(--border);
      display: flex;
      justify-content: flex-start;
      gap: 8px;
      background: var(--border-light);
    }
    /* ============ Forms ============ */
    .form-group { margin-bottom: 16px; }
    .form-label {
      display: block;
      font-size: 13px;
      font-weight: 600;
      color: var(--primary);
      margin-bottom: 6px;
    }
    [data-theme="dark"] .form-label { color: var(--accent-light); }
    .form-label .required { color: var(--danger); }
    .form-input, .form-textarea, .form-select {
      width: 100%;
      padding: 9px 12px;
      border: 1px solid var(--border);
      border-radius: var(--radius-sm);
      font-family: inherit;
      font-size: 13px;
      background: var(--card-bg);
      color: var(--text);
      transition: all 0.2s;
    }
    .form-input:focus, .form-textarea:focus, .form-select:focus {
      outline: none;
      border-color: var(--accent);
      box-shadow: 0 0 0 3px rgba(201,169,97,0.15);
    }
    .form-textarea { resize: vertical; min-height: 70px; line-height: 1.6; }
    .form-row {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 12px;
    }
    .form-hint {
      font-size: 11px;
      color: var(--text-muted);
      margin-top: 4px;
    }
    /* ============ Webhook Section ============ */
    .webhook-section {
      background: var(--border-light);
      border: 1px solid var(--border);
      border-radius: var(--radius-sm);
      padding: 14px;
      margin-top: 12px;
    }
    .webhook-section-header {
      display: flex;
      align-items: center;
      justify-content: space-between;
      margin-bottom: 10px;
    }
    .webhook-section-title {
      font-size: 13px;
      font-weight: 700;
      color: var(--primary);
      display: flex;
      align-items: center;
      gap: 6px;
    }
    [data-theme="dark"] .webhook-section-title { color: var(--accent-light); }
    .webhook-toggle {
      display: flex;
      align-items: center;
      gap: 6px;
      font-size: 11px;
      color: var(--text-muted);
    }
    .switch {
      position: relative;
      display: inline-block;
      width: 36px;
      height: 20px;
    }
    .switch input { opacity: 0; width: 0; height: 0; }
    .slider {
      position: absolute;
      cursor: pointer;
      inset: 0;
      background: var(--border);
      transition: 0.3s;
      border-radius: 20px;
    }
    .slider::before {
      position: absolute;
      content: "";
      height: 14px;
      width: 14px;
      right: 3px;
      bottom: 3px;
      background: white;
      transition: 0.3s;
      border-radius: 50%;
    }
    input:checked + .slider { background: var(--success); }
    input:checked + .slider::before { transform: translateX(-16px); }
    .webhook-status {
      display: flex;
      align-items: center;
      gap: 8px;
      margin-top: 10px;
      padding: 8px 10px;
      background: var(--card-bg);
      border-radius: 4px;
      font-size: 11px;
      color: var(--text-muted);
    }
    .webhook-status-dot {
      width: 8px;
      height: 8px;
      border-radius: 50%;
      background: var(--text-muted);
      flex-shrink: 0;
    }
    .webhook-status-dot.success { background: var(--success); }
    .webhook-status-dot.error { background: var(--danger); }
    .webhook-status-dot.pending { background: var(--warning); animation: pulse 1.5s infinite; }
    @keyframes pulse {
      0%, 100% { opacity: 1; }
      50% { opacity: 0.4; }
    }
    /* ============ Toast ============ */
    .toast-container {
      position: fixed;
      bottom: 20px;
      left: 20px;
      z-index: 2000;
      display: flex;
      flex-direction: column;
      gap: 8px;
    }
    .toast {
      background: var(--primary);
      color: white;
      padding: 12px 18px;
      border-radius: 8px;
      box-shadow: var(--shadow-lg);
      font-size: 13px;
      display: flex;
      align-items: center;
      gap: 10px;
      animation: slideInLeft 0.3s;
      min-width: 250px;
    }
    .toast.success { background: var(--success); }
    .toast.error { background: var(--danger); }
    .toast.warning { background: var(--warning); }
    @keyframes slideInLeft {
      from { opacity: 0; transform: translateX(-30px); }
      to { opacity: 1; transform: translateX(0); }
    }
    /* ============ Footer ============ */
    .app-footer {
      background: linear-gradient(135deg, var(--primary), var(--primary-light));
      color: white;
      padding: 8px 20px;
      font-size: 11px;
      display: flex;
      justify-content: space-between;
      align-items: center;
      border-top: 2px solid var(--accent);
      flex-shrink: 0;
    }
    .footer-brand { color: var(--accent); font-weight: 700; }
    /* ============ Readonly Fields Style ============ */
    .form-input[readonly],
    .form-textarea[readonly] {
      background: var(--border-light);
      color: var(--text-muted);
      cursor: not-allowed;
      opacity: 0.85;
      border-style: dashed;
    }
    .form-input[readonly]:focus,
    .form-textarea[readonly]:focus {
      border-color: var(--border);
      box-shadow: none;
    }
    .readonly-badge {
      display: inline-flex;
      align-items: center;
      gap: 4px;
      font-size: 10px;
      font-weight: 600;
      color: var(--text-muted);
      background: var(--border-light);
      border: 1px solid var(--border);
      padding: 2px 8px;
      border-radius: 10px;
      margin-right: 6px;
    }
    .readonly-badge::before {
      content: '🔒';
      font-size: 10px;
    }
    .readonly-info {
      background: linear-gradient(to left, #ebf8ff, #e6f6ff);
      border: 1px solid #90cdf4;
      border-right: 4px solid var(--info);
      padding: 10px 14px;
      border-radius: 6px;
      font-size: 11px;
      color: var(--text-muted);
      margin-bottom: 14px;
      display: flex;
      align-items: center;
      gap: 8px;
    }
    [data-theme="dark"] .readonly-info {
      background: rgba(49, 130, 206, 0.12);
      border-color: rgba(49, 130, 206, 0.3);
      border-right-color: var(--info);
    }
    /* ============ Scrollbar ============ */
    ::-webkit-scrollbar { width: 8px; height: 8px; }
    ::-webkit-scrollbar-track { background: transparent; }
    ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
    ::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
  </style>
</head>
<body>
  <!-- Header -->
  <header class="app-header">
    <div class="app-brand">
      <div class="app-logo">📊</div>
      <div class="app-brand-text">
        <div class="app-title">پروژه نمونه</div>
        <div class="app-subtitle">توضیحات پروژه</div>
      </div>
    </div>
  </header>

  <!-- Toolbar -->
  <div class="tabs-bar">
    <button class="tab-control-btn" id="sidebarToggle" title="باز/بسته کردن سایدبار">
      <span id="sidebarIcon">☰</span>
    </button>
    <button class="tab-control-btn" id="themeToggle" title="تغییر تم">
      <span id="themeIcon">🌙</span>
    </button>
  </div>

  <!-- Main Layout -->
  <div class="app-main">
    <!-- Sidebar -->
    <aside class="sidebar" id="sidebar">
      <div class="sidebar-header">
        <div class="sidebar-title">🛠️ ابزارها</div>
      </div>
      <div class="sidebar-body" id="sidebarBody"></div>
    </aside>

    <!-- Board Area -->
    <main class="board-area">
      <div class="board-empty">
        <div class="board-empty-icon">📋</div>
        <h2>ناحیه اصلی</h2>
        <p>محتوای اصلی برنامه در این ناحیه نمایش داده می‌شود</p>
      </div>
    </main>
  </div>

  <!-- Footer -->
  <footer class="app-footer">
    <div>
      <span class="footer-brand">طراح و توسعه‌دهنده:</span> هادی خزاعی اصل
      <span style="margin: 0 10px;">|</span>
      <span class="footer-brand">شرکت:</span> رایان پرداز کاوش
    </div>
    <div>
      <span class="footer-brand">نسخه:</span> 1.0.0
    </div>
  </footer>

  <!-- Settings Modal -->
  <div class="modal-overlay" id="settingsModal">
    <div class="modal modal-lg">
      <div class="modal-header">
        <div class="modal-title">
          <span>⚙️</span>
          <span>تنظیمات پروژه</span>
        </div>
        <button class="modal-close" data-close-modal>✕</button>
      </div>
      <div class="modal-body">
        <div class="readonly-info">
          <span>ℹ️</span>
          <span>اطلاعات پایه پروژه (عنوان، نسخه، توضیحات، توسعه‌دهنده و شرکت) فقط قابل مشاهده هستند و از طریق فایل پیکربندی پروژه قابل تغییر می‌باشند.</span>
        </div>

        <div class="form-row">
          <div class="form-group">
            <label class="form-label">
              عنوان پروژه
              <span class="readonly-badge">فقط خواندنی</span>
            </label>
            <input type="text" class="form-input" id="settingProjectTitle" readonly>
          </div>
          <div class="form-group">
            <label class="form-label">
              نسخه
              <span class="readonly-badge">فقط خواندنی</span>
            </label>
            <input type="text" class="form-input" id="settingVersion" readonly>
          </div>
        </div>
        <div class="form-group">
          <label class="form-label">
            توضیحات
            <span class="readonly-badge">فقط خواندنی</span>
          </label>
          <textarea class="form-textarea" id="settingDescription" readonly></textarea>
        </div>
        <div class="form-row">
          <div class="form-group">
            <label class="form-label">
              توسعه‌دهنده
              <span class="readonly-badge">فقط خواندنی</span>
            </label>
            <input type="text" class="form-input" id="settingDeveloper" readonly>
          </div>
          <div class="form-group">
            <label class="form-label">
              شرکت
              <span class="readonly-badge">فقط خواندنی</span>
            </label>
            <input type="text" class="form-input" id="settingCompany" readonly>
          </div>
        </div>

        <div class="webhook-section">
          <div class="webhook-section-header">
            <div class="webhook-section-title">
              <span>🔗</span>
              <span>تنظیمات وب‌هوک (Webhook)</span>
            </div>
            <div class="webhook-toggle">
              <span>فعال‌سازی</span>
              <label class="switch">
                <input type="checkbox" id="settingWebhookEnabled">
                <span class="slider"></span>
              </label>
            </div>
          </div>

          <div class="form-group">
            <label class="form-label">آدرس URL وب‌هوک <span class="required">*</span></label>
            <input type="url" class="form-input" id="settingWebhookUrl" dir="ltr" placeholder="https://example.com/webhook">
            <div class="form-hint">آدرسی که رویدادها به آن ارسال می‌شوند (باید با http یا https شروع شود)</div>
          </div>

          <div class="form-group">
            <label class="form-label">شناسه کلاینت (Client ID) <span class="required">*</span></label>
            <input type="text" class="form-input" id="settingWebhookClientId" dir="ltr" placeholder="client-xxxx-xxxx">
            <div class="form-hint">این شناسه در هدر درخواست با کلید X-Client-Id ارسال می‌شود</div>
          </div>

          <div class="webhook-status" id="webhookStatus">
            <span class="webhook-status-dot" id="webhookStatusDot"></span>
            <span id="webhookStatusText">وضعیت: غیرفعال</span>
          </div>
        </div>
      </div>
      <div class="modal-footer">
        <button class="btn btn-accent" id="saveSettingsBtn">💾 ذخیره</button>
        <button class="btn btn-ghost" data-close-modal>بستن</button>
      </div>
    </div>
  </div>

  <!-- Toast Container -->
  <div class="toast-container" id="toastContainer"></div>

  <script>
    // ============ Project Data Structure (New Schema) ============
    const projectData = {
      projectId: "550e8400-e29b-41d4-a716-446655440000",
      projectName: "sample_project",
      projectTitle: "پروژه نمونه",
      description: "توضیحات پروژه",
      version: "1.0.0",
      developer: "هادی خزاعی اصل",
      company: "رایان پرداز کاوش",
      webhook: {
        url: "",
        clientId: "",
        enabled: false
      },
      toolGroups: [
        {
          id: 'project-management',
          title: 'پروژه',
          icon: '📁',
          description: 'مدیریت چرخه حیات پروژه',
          tools: [
            {
              id: 'project-settings',
              title: 'تنظیمات پروژه',
              icon: '⚙️',
              action: 'openSettings',
              shortcut: 'Ctrl+,',
              description: 'مشاهده اطلاعات پروژه و تنظیمات وب‌هوک',
              targetEntity: 'settings'
            },
            {
              id: 'create-project',
              title: 'ایجاد پروژه جدید',
              icon: '➕',
              action: 'createProject',
              shortcut: 'Ctrl+N',
              description: 'ایجاد یک پروژه جدید با متادیتای اولیه',
              targetEntity: 'projects'
            }
          ]
        },
        {
          id: 'data-analytics',
          title: 'ابزارهای تحلیل داده',
          icon: '📊',
          description: 'ابزارهای تحلیل و پردازش داده‌ها',
          tools: [
            {
              id: 'chart-generator',
              title: 'نمودار ساز پویا',
              icon: '📈',
              action: 'generateChart',
              shortcut: 'Ctrl+G',
              description: 'ابزاری برای تبدیل جداول داده به نمودارهای میله‌ای و دایره‌ای به صورت زنده',
              targetEntity: 'charts'
            },
            {
              id: 'advanced-filter',
              title: 'فیلتر پیشرفته',
              icon: '🔍',
              action: 'applyFilter',
              shortcut: 'Ctrl+F',
              description: 'امکان اعمال فیلترهای چندلایه و شرطی بر روی مجموعه داده‌ها',
              targetEntity: 'datasets'
            }
          ]
        },
        {
          id: 'export-tools',
          title: 'ابزارهای خروجی و گزارش',
          icon: '📤',
          description: 'ابزارهای تولید گزارش و خروجی',
          tools: [
            {
              id: 'pdf-export',
              title: 'خروجی PDF',
              icon: '📄',
              action: 'exportPDF',
              shortcut: 'Ctrl+E',
              description: 'تولید فایل PDF استاندارد از گزارش‌های فعلی با قابلیت شخصی‌سازی هدر و فوتر',
              targetEntity: 'reports'
            }
          ]
        }
      ]
    };

    // ============ Action Registry ============
    const ActionRegistry = {
      openSettings(tool, group) {
        openSettingsModal();
      },
      createProject(tool, group) {
        showToast(`🚀 اکشن "${tool.action}" اجرا شد - ${tool.title}`, 'info', 3000);
      },
      generateChart(tool, group) {
        showToast(`📈 اکشن "${tool.action}" اجرا شد - ${tool.title}`, 'info', 3000);
      },
      applyFilter(tool, group) {
        showToast(`🔍 اکشن "${tool.action}" اجرا شد - ${tool.title}`, 'info', 3000);
      },
      exportPDF(tool, group) {
        showToast(`📄 اکشن "${tool.action}" اجرا شد - ${tool.title}`, 'info', 3000);
      },
      defaultAction(tool, group) {
        showToast(`ابزار انتخاب شد: ${tool.title}`, 'info', 3000);
      }
    };

    // ============ Toast System ============
    function showToast(message, type = 'info', duration = 3000) {
      const container = document.getElementById('toastContainer');
      const toast = document.createElement('div');
      toast.className = `toast ${type}`;
      
      const icons = {
        success: '✅',
        error: '❌',
        warning: '⚠️',
        info: 'ℹ️'
      };
      
      toast.innerHTML = `
        <span>${icons[type] || 'ℹ️'}</span>
        <span>${message}</span>
      `;
      
      container.appendChild(toast);
      
      setTimeout(() => {
        toast.style.transition = 'opacity 0.3s, transform 0.3s';
        toast.style.opacity = '0';
        toast.style.transform = 'translateX(-30px)';
        setTimeout(() => toast.remove(), 300);
      }, duration);
    }

    // ============ Webhook Manager ============
    const WebhookManager = {
      getConfig() {
        return projectData.webhook;
      },

      setConfig(config) {
        projectData.webhook = { ...projectData.webhook, ...config };
        this.updateStatusUI();
      },

      isValid() {
        const cfg = this.getConfig();
        if (!cfg.enabled) return false;
        if (!cfg.url || !cfg.clientId) return false;
        if (!/^https?:\/\//i.test(cfg.url)) return false;
        return true;
      },

      updateStatusUI() {
        const cfg = this.getConfig();
        const dot = document.getElementById('webhookStatusDot');
        const text = document.getElementById('webhookStatusText');
        
        if (!dot || !text) return;
        
        dot.className = 'webhook-status-dot';
        
        if (!cfg.enabled) {
          text.textContent = 'وضعیت: غیرفعال';
        } else if (!cfg.url || !cfg.clientId) {
          dot.classList.add('pending');
          text.textContent = 'وضعیت: تنظیمات ناقص';
        } else if (!/^https?:\/\//i.test(cfg.url)) {
          dot.classList.add('error');
          text.textContent = 'وضعیت: آدرس نامعتبر';
        } else {
          dot.classList.add('success');
          text.textContent = 'وضعیت: آماده ارسال';
        }
      },

      async sendEvent(eventName, payload = {}) {
        if (!this.isValid()) {
          console.warn('[Webhook] ارسال انجام نشد - تنظیمات معتبر نیست');
          return { success: false, reason: 'invalid_config' };
        }

        const cfg = this.getConfig();
        const requestBody = {
          event: eventName,
          timestamp: new Date().toISOString(),
          projectId: projectData.projectId,
          projectName: projectData.projectName,
          projectVersion: projectData.version,
          clientId: cfg.clientId,
          data: payload
        };

        try {
          const response = await fetch(cfg.url, {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
              'X-Client-Id': cfg.clientId,
              'X-Event-Name': eventName,
              'X-Project-Id': projectData.projectId
            },
            body: JSON.stringify(requestBody)
          });

          const success = response.ok;
          if (success) {
            console.log(`[Webhook] رویداد "${eventName}" با موفقیت ارسال شد`);
          } else {
            console.error(`[Webhook] خطا در ارسال: ${response.status}`);
          }
          return { success, status: response.status };
        } catch (err) {
          console.error('[Webhook] خطای شبکه:', err);
          return { success: false, reason: 'network_error', error: err.message };
        }
      }
    };

    // ============ Tool Selection Handler ============
    function handleToolSelect(tool, group) {
      const actionHandler = ActionRegistry[tool.action] || ActionRegistry.defaultAction;
      actionHandler(tool, group);
      
      const eventPayload = {
        toolId: tool.id,
        toolTitle: tool.title,
        toolIcon: tool.icon,
        toolAction: tool.action,
        toolDescription: tool.description,
        targetEntity: tool.targetEntity,
        groupId: group.id,
        groupTitle: group.title,
        selectedAt: new Date().toISOString()
      };
      
      WebhookManager.sendEvent('tool_selected', eventPayload);
    }

    // ============ Theme Toggle ============
    const themeToggle = document.getElementById('themeToggle');
    const themeIcon = document.getElementById('themeIcon');
    const html = document.documentElement;

    const savedTheme = localStorage.getItem('theme') || 'light';
    html.setAttribute('data-theme', savedTheme);
    updateThemeUI(savedTheme);

    themeToggle.addEventListener('click', () => {
      const currentTheme = html.getAttribute('data-theme');
      const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
      html.setAttribute('data-theme', newTheme);
      localStorage.setItem('theme', newTheme);
      updateThemeUI(newTheme);
    });

    function updateThemeUI(theme) {
      themeIcon.textContent = theme === 'dark' ? '☀️' : '🌙';
    }

    // ============ Sidebar Toggle ============
    const sidebarToggle = document.getElementById('sidebarToggle');
    const sidebar = document.getElementById('sidebar');
    const sidebarIcon = document.getElementById('sidebarIcon');

    sidebarToggle.addEventListener('click', () => {
      sidebar.classList.toggle('collapsed');
      sidebarIcon.textContent = sidebar.classList.contains('collapsed') ? '☰' : '✕';
    });

    // ============ Render Tool Groups ============
    const sidebarBody = document.getElementById('sidebarBody');

    function renderToolGroups() {
      sidebarBody.innerHTML = '';
      
      projectData.toolGroups.forEach(group => {
        const groupElement = document.createElement('div');
        groupElement.className = 'sidebar-group';
        groupElement.dataset.groupId = group.id;

        const headerElement = document.createElement('div');
        headerElement.className = 'sidebar-group-header';
        
        // Add tooltip if description exists
        if (group.description) {
          headerElement.setAttribute('data-tooltip', group.description);
        }
        
        headerElement.innerHTML = `
          <div class="group-icon">
            <span>${group.icon || '📁'}</span>
            <span>${group.title}</span>
          </div>
          <span class="group-arrow">▼</span>
        `;

        const bodyElement = document.createElement('div');
        bodyElement.className = 'sidebar-group-body';

        group.tools.forEach(tool => {
          const toolBtn = document.createElement('button');
          toolBtn.className = 'sidebar-btn';
          toolBtn.dataset.toolId = tool.id;
          toolBtn.title = tool.description;
          
          toolBtn.innerHTML = `
            <span class="btn-icon-emoji">${tool.icon || '🔧'}</span>
            <span class="btn-title">${tool.title}</span>
            ${tool.shortcut ? `<span class="shortcut-badge">${tool.shortcut}</span>` : ''}
          `;
          
          toolBtn.addEventListener('click', () => {
            handleToolSelect(tool, group);
          });

          bodyElement.appendChild(toolBtn);
        });

        headerElement.addEventListener('click', () => {
          groupElement.classList.toggle('collapsed');
        });

        groupElement.appendChild(headerElement);
        groupElement.appendChild(bodyElement);
        sidebarBody.appendChild(groupElement);
      });
    }

    // ============ Settings Modal ============
    const settingsModal = document.getElementById('settingsModal');
    const saveSettingsBtn = document.getElementById('saveSettingsBtn');

    function openSettingsModal() {
      document.getElementById('settingProjectTitle').value = projectData.projectTitle;
      document.getElementById('settingVersion').value = projectData.version;
      document.getElementById('settingDescription').value = projectData.description;
      document.getElementById('settingDeveloper').value = projectData.developer;
      document.getElementById('settingCompany').value = projectData.company;
      
      document.getElementById('settingWebhookUrl').value = projectData.webhook.url;
      document.getElementById('settingWebhookClientId').value = projectData.webhook.clientId;
      document.getElementById('settingWebhookEnabled').checked = projectData.webhook.enabled;
      
      WebhookManager.updateStatusUI();
      settingsModal.classList.add('active');
    }

    function closeSettingsModal() {
      settingsModal.classList.remove('active');
    }

    document.querySelectorAll('[data-close-modal]').forEach(btn => {
      btn.addEventListener('click', closeSettingsModal);
    });

    settingsModal.addEventListener('click', (e) => {
      if (e.target === settingsModal) closeSettingsModal();
    });

    ['settingProjectTitle', 'settingVersion', 'settingDescription', 'settingDeveloper', 'settingCompany'].forEach(id => {
      const el = document.getElementById(id);
      el.addEventListener('keydown', (e) => {
        if (!e.ctrlKey && !e.metaKey && e.key.length === 1) {
          e.preventDefault();
        }
      });
      el.addEventListener('paste', (e) => e.preventDefault());
    });

    ['settingWebhookUrl', 'settingWebhookClientId', 'settingWebhookEnabled'].forEach(id => {
      document.getElementById(id).addEventListener('input', () => {
        WebhookManager.setConfig({
          url: document.getElementById('settingWebhookUrl').value,
          clientId: document.getElementById('settingWebhookClientId').value,
          enabled: document.getElementById('settingWebhookEnabled').checked
        });
      });
    });

    saveSettingsBtn.addEventListener('click', () => {
      WebhookManager.setConfig({
        url: document.getElementById('settingWebhookUrl').value.trim(),
        clientId: document.getElementById('settingWebhookClientId').value.trim(),
        enabled: document.getElementById('settingWebhookEnabled').checked
      });
      
      closeSettingsModal();
      
      showToast('تنظیمات با موفقیت ذخیره شد', 'success');
      
      WebhookManager.sendEvent('settings_updated', {
        updatedFields: ['webhook']
      });
    });

    // ============ Update Project UI ============
    function updateProjectUI() {
      document.title = projectData.projectTitle;
      document.querySelector('.app-title').textContent = projectData.projectTitle;
      document.querySelector('.app-subtitle').textContent = projectData.description;
      document.querySelector('.app-footer').innerHTML = `
        <div>
          <span class="footer-brand">طراح و توسعه‌دهنده:</span> ${projectData.developer}
          <span style="margin: 0 10px;">|</span>
          <span class="footer-brand">شرکت:</span> ${projectData.company}
        </div>
        <div>
          <span class="footer-brand">نسخه:</span> ${projectData.version}
        </div>
      `;
    }

    // ============ Keyboard Shortcuts ============
    document.addEventListener('keydown', (e) => {
      if (!(e.ctrlKey || e.metaKey)) return;
      
      const key = e.key.toLowerCase();
      
      for (const group of projectData.toolGroups) {
        const tool = group.tools.find(t => t.shortcut && 
          t.shortcut.toLowerCase().replace('ctrl+', '') === key);
        if (tool) {
          e.preventDefault();
          handleToolSelect(tool, group);
          break;
        }
      }
    });

    // ============ Initialize ============
    renderToolGroups();
    updateProjectUI();
    WebhookManager.updateStatusUI();
    
    setTimeout(() => {
      showToast(`${projectData.projectTitle} آماده استفاده است`, 'success');
    }, 500);
  </script>
</body>
</html>

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Resources\Tools\xUMLDrawer.html

<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ابزار رسم نمودارهای UML</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource/vazirmatn@5.0.0/index.css">
<style>
body{font-family:'Vazirmatn',sans-serif;overflow:hidden}
:root{
--bg-primary:#f7fafc;--bg-secondary:#fff;--bg-tertiary:#edf2f7;--bg-hover:#f0f4f8;
--text-primary:#2d3748;--text-secondary:#4a5568;--text-muted:#718096;--border-color:#e2e8f0;
--sidebar-bg:#fff;--canvas-bg:#fff;--grid-line:rgba(0,0,0,.06);--tab-bg:#fff;
--tab-bar-bg:linear-gradient(to bottom,#f8fafc,#f1f5f9);--status-bg:#2d3748;--status-text:#fff;
--modal-bg:#fff;--modal-overlay:rgba(0,0,0,.5);--input-bg:#fff;--input-border:#cbd5e0;
--input-readonly-bg:#f7fafc;--card-bg:#f7fafc;--card-border:#e2e8f0;--section-border:#edf2f7;
--shadow-sm:0 1px 3px rgba(0,0,0,.08);--shadow-md:0 4px 12px rgba(0,0,0,.1);
--shadow-lg:0 10px 30px rgba(0,0,0,.12);
--actor-stroke:#2d3748;--uc-fill:#f7fafc;--uc-stroke:#2d3748;--rel-stroke:#2d3748;
--sb-stroke:#4a5568;--label-bg:#fff;--state-fill:#ebf8ff;--state-stroke:#2b6cb0;
--state-init:#2d3748;--state-final-outer:#2d3748;--state-final-inner:#2d3748;
--transition-stroke:#4a5568;--transition-label:#2d3748;--activity-fill:#f0fff4;
--activity-stroke:#276749;--decision-fill:#fffaf0;--decision-stroke:#c05621;
--fork-stroke:#2d3748;--flow-stroke:#4a5568;--flow-label:#2d3748;
--comp-fill:#faf5ff;--comp-stroke:#6b46c1;--comp-icon:#6b46c1;--iface-stroke:#6b46c1;
--pkg-fill:#fffaf0;--pkg-stroke:#c05621;--dep-stroke:#4a5568;
--node-face-top:#e6fffa;--node-face-front:#b2f5ea;--node-face-side:#81e6d9;
--node-stroke:#234e52;--artifact-fill:#fff5f5;--artifact-stroke:#c53030;
--class-fill:#ebf8ff;--class-stroke:#2c5282;--class-header:#bee3f8;
--class-attr:#2c5282;--class-method:#2f855a;
--seq-lifeline:#4a5568;--seq-participant-fill:#e2e8f0;--seq-participant-stroke:#2d3748;
--seq-activation:#bee3f8;--seq-activation-stroke:#2c5282;--seq-msg:#2d3748;--seq-return:#718096;
--seq-fragment-fill:#fffaf0;--seq-fragment-stroke:#c05621;
--swimlane-default:#ebf8ff;--swimlane-stroke:#2c5282;
}
body.dark{
--bg-primary:#0f1724;--bg-secondary:#1a2332;--bg-tertiary:#243044;--bg-hover:#2a3a52;
--text-primary:#e2e8f0;--text-secondary:#cbd5e0;--text-muted:#8899aa;--border-color:#2d3f56;
--sidebar-bg:#1a2332;--canvas-bg:#0f1724;--grid-line:rgba(255,255,255,.05);--tab-bg:#1a2332;
--tab-bar-bg:linear-gradient(to bottom,#1a2332,#162030);--status-bg:#0a1018;--status-text:#cbd5e0;
--modal-bg:#1a2332;--modal-overlay:rgba(0,0,0,.7);--input-bg:#243044;--input-border:#3a5068;
--input-readonly-bg:#1e2d40;--card-bg:#1e2d40;--card-border:#2d3f56;--section-border:#2d3f56;
--shadow-sm:0 1px 3px rgba(0,0,0,.3);--shadow-md:0 4px 12px rgba(0,0,0,.4);
--shadow-lg:0 10px 30px rgba(0,0,0,.5);
--actor-stroke:#cbd5e0;--uc-fill:#1e2d40;--uc-stroke:#cbd5e0;--rel-stroke:#a0b4c8;
--sb-stroke:#8899aa;--label-bg:#1a2332;--state-fill:#1e2d40;--state-stroke:#63b3ed;
--state-init:#e2e8f0;--state-final-outer:#e2e8f0;--state-final-inner:#e2e8f0;
--transition-stroke:#8899aa;--transition-label:#cbd5e0;--activity-fill:#1e2d40;
--activity-stroke:#68d391;--decision-fill:#2d2418;--decision-stroke:#fbd38d;
--fork-stroke:#e2e8f0;--flow-stroke:#8899aa;--flow-label:#cbd5e0;
--comp-fill:#2a1f3d;--comp-stroke:#b794f4;--comp-icon:#b794f4;--iface-stroke:#b794f4;
--pkg-fill:#2d2418;--pkg-stroke:#fbd38d;--dep-stroke:#8899aa;
--node-face-top:#234e52;--node-face-front:#1d4044;--node-face-side:#1a3a3e;
--node-stroke:#81e6d9;--artifact-fill:#3b1a1a;--artifact-stroke:#fc8181;
--class-fill:#1e2d40;--class-stroke:#63b3ed;--class-header:#2c5282;
--class-attr:#90cdf4;--class-method:#9ae6b4;
--seq-lifeline:#8899aa;--seq-participant-fill:#243044;--seq-participant-stroke:#cbd5e0;
--seq-activation:#2c5282;--seq-activation-stroke:#63b3ed;--seq-msg:#cbd5e0;--seq-return:#8899aa;
--seq-fragment-fill:#2d2418;--seq-fragment-stroke:#fbd38d;
--swimlane-default:#1e2d40;--swimlane-stroke:#63b3ed;
}
body{background:var(--bg-primary);color:var(--text-primary)}
.tool-btn{transition:all .2s;background:var(--bg-tertiary);color:var(--text-secondary);border:1px solid transparent}
.tool-btn:hover{transform:translateY(-1px);box-shadow:var(--shadow-sm);background:var(--bg-hover)}
.tool-btn.active{background:linear-gradient(135deg,#667eea 0%,#764ba2 100%)!important;color:#fff!important;border-color:transparent!important}
body.dark .tool-btn-red{background:#3b1a1a!important;color:#fc8181!important}
body.dark .tool-btn-orange{background:#3b2a1a!important;color:#fbd38d!important}
body.dark .tool-btn-purple{background:#2a1a3b!important;color:#d6bcfa!important}
body.dark .tool-btn-teal{background:#1a3b35!important;color:#81e6d9!important}
body.dark .tool-btn-cyan{background:#1a2e3b!important;color:#76e4f7!important}
body.dark .tool-btn-green{background:#1a3b1e!important;color:#9ae6b4!important}
body.dark .tool-btn-blue{background:#1a253b!important;color:#90cdf4!important}
body.dark .tool-btn-indigo{background:#1e2544!important;color:#a3bffa!important}
.tool-btn-red{background:#fff5f5!important;color:#c53030!important}
.tool-btn-orange{background:#fffaf0!important;color:#c05621!important}
.tool-btn-purple{background:#faf5ff!important;color:#6b46c1!important}
.tool-btn-teal{background:#e6fffa!important;color:#285e61!important}
.tool-btn-cyan{background:#edfdfd!important;color:#0987a0!important}
.tool-btn-green{background:#f0fff4!important;color:#276749!important}
.tool-btn-blue{background:#ebf8ff!important;color:#2b6cb0!important}
.tool-btn-indigo{background:#ebf4ff!important;color:#4c51bf!important}
canvas{cursor:crosshair}
.canvas-container{background-color:var(--canvas-bg)}
.sidebar-wrapper{transition:width .3s cubic-bezier(.4,0,.2,1),opacity .2s ease;width:260px;overflow:hidden;flex-shrink:0;background:var(--sidebar-bg);border-left:1px solid var(--border-color)}
.sidebar-wrapper.collapsed{width:0;opacity:0;pointer-events:none}
.sidebar-inner{width:260px;height:100%;overflow-y:auto}
.tool-group{margin-bottom:12px}
.tool-group-header{display:flex;align-items:center;gap:6px;font-size:11px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.5px;padding-bottom:6px;margin-bottom:6px;border-bottom:1px solid var(--section-border);cursor:pointer;user-select:none;transition:color .2s}
.tool-group-header:hover{color:var(--text-secondary)}
.tool-group-arrow{font-size:10px;transition:transform .3s ease;color:var(--text-muted)}
.tool-group.collapsed .tool-group-arrow{transform:rotate(-90deg)}
.tool-group-content{max-height:1500px;overflow:hidden;transition:max-height .35s ease,opacity .25s ease;opacity:1}
.tool-group.collapsed .tool-group-content{max-height:0;opacity:0}
.properties-overlay{position:fixed;bottom:40px;left:50%;transform:translateX(-50%) translateY(120%);z-index:1000;transition:transform .35s cubic-bezier(.4,0,.2,1),opacity .35s ease;opacity:0;pointer-events:none;min-width:520px;max-width:90vw}
.properties-overlay.visible{transform:translateX(-50%) translateY(0);opacity:1;pointer-events:auto}
.properties-overlay .panel-shadow{background:var(--modal-bg);box-shadow:0 -8px 30px rgba(0,0,0,.25),0 0 0 1px var(--border-color)}
.modal-overlay{position:fixed;inset:0;background:var(--modal-overlay);backdrop-filter:blur(4px);z-index:2000;display:flex;align-items:center;justify-content:center;opacity:0;pointer-events:none;transition:opacity .3s ease}
.modal-overlay.visible{opacity:1;pointer-events:auto}
.modal-content{background:var(--modal-bg);border-radius:16px;box-shadow:var(--shadow-lg);border:1px solid var(--border-color);max-width:600px;width:90%;max-height:80vh;overflow-y:auto;transform:scale(.9) translateY(20px);transition:transform .3s cubic-bezier(.4,0,.2,1);color:var(--text-primary)}
.modal-overlay.visible .modal-content{transform:scale(1) translateY(0)}
.save-modal-content{max-width:520px}
.class-editor-modal{max-width:720px}
.tab-bar{background:var(--tab-bar-bg);border-bottom:1px solid var(--border-color);display:flex;align-items:center;padding:0 8px;flex-shrink:0;height:44px;gap:4px}
.tabs-scroll{display:flex;overflow-x:auto;overflow-y:hidden;flex:1;gap:4px;padding:6px 4px;scrollbar-width:thin}
.tabs-scroll::-webkit-scrollbar{height:4px}
.tabs-scroll::-webkit-scrollbar-thumb{background:var(--border-color);border-radius:2px}
.diagram-tab{display:flex;align-items:center;gap:6px;padding:6px 10px 6px 12px;background:var(--tab-bg);border:1px solid var(--border-color);border-radius:8px 8px 0 0;cursor:pointer;min-width:140px;max-width:240px;transition:all .2s;user-select:none;flex-shrink:0}
.diagram-tab:hover{background:var(--bg-hover)}
.diagram-tab.active{background:var(--bg-secondary);border-color:#667eea;border-bottom:2px solid var(--bg-secondary);margin-bottom:-1px;box-shadow:0 -2px 8px rgba(102,126,234,.15)}
.diagram-tab.active .tab-title{color:#667eea;font-weight:600}
.tab-title{font-size:12px;color:var(--text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1}
.tab-type-badge{font-size:9px;padding:1px 5px;border-radius:4px;background:var(--bg-tertiary);color:var(--text-muted);font-weight:600;flex-shrink:0}
.tab-close{width:18px;height:18px;display:flex;align-items:center;justify-content:center;border-radius:4px;color:var(--text-muted);font-size:14px;transition:all .15s}
.tab-close:hover{background:#fed7d7;color:#c53030}
.header-bar-btn{width:32px;height:32px;display:flex;align-items:center;justify-content:center;color:#fff;border-radius:8px;cursor:pointer;font-size:16px;transition:all .2s;flex-shrink:0}
.header-bar-btn:hover{transform:scale(1.08)}
.btn-new{background:linear-gradient(135deg,#48bb78,#38a169);box-shadow:0 2px 4px rgba(72,187,120,.3)}
.btn-toggle{background:linear-gradient(135deg,#667eea,#764ba2);box-shadow:0 2px 4px rgba(102,126,234,.3)}
.btn-theme{background:linear-gradient(135deg,#ed8936,#dd6b20);box-shadow:0 2px 4px rgba(237,137,54,.3);font-size:18px}
.btn-undo{background:linear-gradient(135deg,#4299e1,#3182ce);box-shadow:0 2px 4px rgba(66,153,225,.3);font-size:14px}
.btn-redo{background:linear-gradient(135deg,#4299e1,#3182ce);box-shadow:0 2px 4px rgba(66,153,225,.3);font-size:14px}
.btn-zoom{background:linear-gradient(135deg,#9f7aea,#805ad5);box-shadow:0 2px 4px rgba(159,122,234,.3);font-size:14px;width:28px;height:28px}
.zoom-display{font-size:11px;color:var(--text-muted);min-width:45px;text-align:center;font-family:monospace;background:var(--bg-tertiary);padding:4px 6px;border-radius:6px;cursor:pointer;user-select:none}
.zoom-display:hover{background:var(--bg-hover);color:var(--text-secondary)}
.themed-input{background:var(--input-bg);border:1px solid var(--input-border);color:var(--text-primary);border-radius:8px;padding:8px 12px;font-size:13px;transition:border-color .2s,box-shadow .2s;width:100%}
.themed-input:focus{outline:none;border-color:#667eea;box-shadow:0 0 0 3px rgba(102,126,234,.15)}
.themed-input-readonly{background:var(--input-readonly-bg);border:1px solid var(--border-color);color:var(--text-muted);border-radius:8px;padding:8px 12px;font-size:13px;width:100%}
.themed-card{background:var(--card-bg);border:1px solid var(--card-border);border-radius:8px;padding:12px}
.status-bar{background:var(--status-bg);color:var(--status-text);border-top:1px solid var(--border-color)}
.main-header{background:linear-gradient(to left,#4c51bf,#6b46c1);box-shadow:var(--shadow-md)}
.grid-type-selector{display:flex;background:var(--bg-tertiary);border:1px solid var(--border-color);border-radius:8px;padding:2px;gap:2px}
.grid-type-option{flex:1;display:flex;align-items:center;justify-content:center;gap:4px;padding:6px 8px;border-radius:6px;cursor:pointer;font-size:11px;font-weight:500;color:var(--text-muted);transition:all .2s;user-select:none}
.grid-type-option:hover{color:var(--text-secondary);background:var(--bg-hover)}
.grid-type-option.active{background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);color:#fff;box-shadow:0 2px 4px rgba(102,126,234,.25)}
.diagram-type-card{border:2px solid var(--border-color);border-radius:12px;padding:16px;cursor:pointer;transition:all .2s;background:var(--card-bg)}
.diagram-type-card:hover{border-color:#667eea;transform:translateY(-2px);box-shadow:var(--shadow-md)}
.diagram-type-card.selected{border-color:#667eea;background:linear-gradient(135deg,rgba(102,126,234,.08),rgba(118,75,162,.08));box-shadow:0 0 0 3px rgba(102,126,234,.15)}
.diagram-type-card.disabled{opacity:.45;cursor:not-allowed;filter:grayscale(.5)}
.diagram-type-card.disabled:hover{border-color:var(--border-color);transform:none;box-shadow:none}
.diagram-type-icon{width:48px;height:48px;border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:24px;background:linear-gradient(135deg,#667eea,#764ba2);color:#fff;margin-bottom:10px}
.diagram-type-card.disabled .diagram-type-icon{background:var(--bg-tertiary)}
.file-preview{background:linear-gradient(135deg,rgba(102,126,234,.08),rgba(118,75,162,.08));border:1px dashed #667eea;border-radius:12px;padding:14px;display:flex;align-items:center;gap:12px}
.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--text-muted)}
.class-editor-tabs{display:flex;border-bottom:2px solid var(--border-color);margin-bottom:16px}
.class-editor-tab{flex:1;padding:10px;text-align:center;cursor:pointer;font-size:13px;font-weight:500;color:var(--text-muted);border-bottom:2px solid transparent;margin-bottom:-2px;transition:all .2s}
.class-editor-tab:hover{color:var(--text-secondary)}
.class-editor-tab.active{color:#667eea;border-bottom-color:#667eea}
.member-table{width:100%;border-collapse:collapse;font-size:12px}
.member-table th{background:var(--bg-tertiary);color:var(--text-muted);padding:6px 8px;text-align:right;font-weight:600;border-bottom:1px solid var(--border-color)}
.member-table td{padding:4px;border-bottom:1px solid var(--border-color)}
.member-table input,.member-table select{background:var(--input-bg);border:1px solid var(--input-border);color:var(--text-primary);border-radius:4px;padding:4px 6px;font-size:12px;width:100%}
.member-table input:focus,.member-table select:focus{outline:none;border-color:#667eea}
.member-btn{padding:4px 8px;border-radius:4px;font-size:11px;cursor:pointer;border:none;transition:all .15s}
.member-btn-add{background:#48bb78;color:#fff}
.member-btn-add:hover{background:#38a169}
.member-btn-del{background:#fc8181;color:#fff}
.member-btn-del:hover{background:#f56565}
@media print{.no-print{display:none!important}canvas{page-break-inside:avoid}}
</style>
</head>
<body class="dark">
<div class="flex flex-col h-screen">
<header class="main-header text-white flex-shrink-0 no-print">
<div class="px-4 py-3">
<div class="flex justify-between items-center">
<div>
<h1 class="text-xl font-bold">🎯 ابزار رسم نمودارهای UML</h1>
<p class="text-xs opacity-90 mt-1">مدیریت چند نمودار با قابلیت ذخیره و بازیابی</p>
</div>
<div class="text-left text-sm"><div class="opacity-90">طراح: هادی خزاعی اصل</div></div>
</div>
</div>
</header>

<div class="tab-bar no-print">
<button id="toggleSidebarBtn" class="header-bar-btn btn-toggle" title="نمایش/مخفی ابزارها (B)">☰</button>
<button id="newDiagramBtn" class="header-bar-btn btn-new" title="دیاگرام جدید (Ctrl+N)">+</button>
<button id="themeToggleBtn" class="header-bar-btn btn-theme" title="تغییر تم">🌙</button>
<div style="width:1px;height:24px;background:var(--border-color);margin:0 4px"></div>
<button id="undoBtn" class="header-bar-btn btn-undo" title="بازگشت (Ctrl+Z)">↶</button>
<button id="redoBtn" class="header-bar-btn btn-redo" title="از نو (Ctrl+Y)">↷</button>
<div style="width:1px;height:24px;background:var(--border-color);margin:0 4px"></div>
<button id="zoomOutBtn" class="header-bar-btn btn-zoom" title="کوچک‌نمایی">−</button>
<div id="zoomDisplay" class="zoom-display" title="کلیک برای بازنشانی">100%</div>
<button id="zoomInBtn" class="header-bar-btn btn-zoom" title="بزرگ‌نمایی">+</button>
<div style="width:1px;height:24px;background:var(--border-color);margin:0 4px"></div>
<div id="tabsContainer" class="tabs-scroll"></div>
</div>

<div class="flex flex-1 overflow-hidden">
<div id="sidebarWrapper" class="sidebar-wrapper no-print">
<div class="sidebar-inner p-3">
<h2 class="font-bold mb-3 text-base" style="color:var(--text-primary)">🛠️ ابزارها</h2>

<div class="tool-group collapsed" data-group="selection">
<div class="tool-group-header"><span>🖱️</span><span style="flex:1">انتخاب</span><span class="tool-group-arrow">▼</span></div>
<div class="tool-group-content">
<button class="tool-btn active w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="select" data-types="all"><span>🖱️</span><span>انتخاب و جابجایی</span></button>
</div>
</div>

<div class="tool-group collapsed" data-group="uc-elements" data-diagram-types="usecase">
<div class="tool-group-header"><span>📐</span><span style="flex:1">المان‌های Use Case</span><span class="tool-group-arrow">▼</span></div>
<div class="tool-group-content">
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="actor" data-types="usecase"><span>👤</span><span>Actor</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="useCase" data-types="usecase"><span>⭕</span><span>Use Case</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="boundary" data-types="usecase"><span>📦</span><span>System Boundary</span></button>
</div>
</div>
<div class="tool-group collapsed" data-group="uc-relations" data-diagram-types="usecase">
<div class="tool-group-header"><span>🔗</span><span style="flex:1">روابط Use Case</span><span class="tool-group-arrow">▼</span></div>
<div class="tool-group-content">
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="association" data-types="usecase"><span>➖</span><span>Association</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="include" data-types="usecase"><span>🔗</span><span>Include</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="extend" data-types="usecase"><span>🔀</span><span>Extend</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="generalization" data-types="usecase"><span>🔺</span><span>Generalization</span></button>
</div>
</div>

<div class="tool-group collapsed" data-group="sm-elements" data-diagram-types="statemachine">
<div class="tool-group-header"><span>🔵</span><span style="flex:1">المان‌های State Machine</span><span class="tool-group-arrow">▼</span></div>
<div class="tool-group-content">
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="initialState" data-types="statemachine"><span>⚫</span><span>Initial State</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="state" data-types="statemachine"><span>🔲</span><span>State</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="finalState" data-types="statemachine"><span>🎯</span><span>Final State</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="compositeState" data-types="statemachine"><span>📦</span><span>Composite State</span></button>
</div>
</div>
<div class="tool-group collapsed" data-group="sm-relations" data-diagram-types="statemachine">
<div class="tool-group-header"><span>➡️</span><span style="flex:1">روابط State Machine</span><span class="tool-group-arrow">▼</span></div>
<div class="tool-group-content">
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="transition" data-types="statemachine"><span>➡️</span><span>Transition</span></button>
</div>
</div>

<div class="tool-group collapsed" data-group="act-elements" data-diagram-types="activity">
<div class="tool-group-header"><span>🔷</span><span style="flex:1">المان‌های Activity</span><span class="tool-group-arrow">▼</span></div>
<div class="tool-group-content">
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="initialNode" data-types="activity"><span>⚫</span><span>Initial Node</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="finalNode" data-types="activity"><span>🎯</span><span>Final Node</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="action" data-types="activity"><span>▭</span><span>Action</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="decision" data-types="activity"><span>◇</span><span>Decision</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="merge" data-types="activity"><span>◇</span><span>Merge</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="forkJoin" data-types="activity"><span>▬</span><span>Fork/Join</span></button>
</div>
</div>
<div class="tool-group collapsed" data-group="act-flows" data-diagram-types="activity">
<div class="tool-group-header"><span>➡️</span><span style="flex:1">جریان‌های Activity</span><span class="tool-group-arrow">▼</span></div>
<div class="tool-group-content">
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="controlFlow" data-types="activity"><span>→</span><span>Control Flow</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="objectFlow" data-types="activity"><span>⇢</span><span>Object Flow</span></button>
</div>
</div>

<div class="tool-group collapsed" data-group="comp-elements" data-diagram-types="component">
<div class="tool-group-header"><span>🧩</span><span style="flex:1">المان‌های Component</span><span class="tool-group-arrow">▼</span></div>
<div class="tool-group-content">
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="component" data-types="component"><span>🧩</span><span>Component</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="interfaceProv" data-types="component"><span>🍭</span><span>Interface Provided</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="interfaceReq" data-types="component"><span>◗</span><span>Interface Required</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="package" data-types="component"><span>📁</span><span>Package</span></button>
</div>
</div>
<div class="tool-group collapsed" data-group="comp-relations" data-diagram-types="component">
<div class="tool-group-header"><span>🔗</span><span style="flex:1">روابط Component</span><span class="tool-group-arrow">▼</span></div>
<div class="tool-group-content">
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="dependency" data-types="component"><span>⇢</span><span>Dependency</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="connector" data-types="component"><span>—</span><span>Connector</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="compAssociation" data-types="component"><span>➖</span><span>Association</span></button>
</div>
</div>

<div class="tool-group collapsed" data-group="dep-elements" data-diagram-types="deployment">
<div class="tool-group-header"><span>🖥️</span><span style="flex:1">المان‌های Deployment</span><span class="tool-group-arrow">▼</span></div>
<div class="tool-group-content">
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="node" data-types="deployment"><span>🖥️</span><span>Node</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="device" data-types="deployment"><span>💻</span><span>Device</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="execEnv" data-types="deployment"><span>⚙️</span><span>Execution Env</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="artifact" data-types="deployment"><span>📄</span><span>Artifact</span></button>
</div>
</div>
<div class="tool-group collapsed" data-group="dep-relations" data-diagram-types="deployment">
<div class="tool-group-header"><span>🔗</span><span style="flex:1">روابط Deployment</span><span class="tool-group-arrow">▼</span></div>
<div class="tool-group-content">
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="commPath" data-types="deployment"><span>⇌</span><span>Communication Path</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="deployRel" data-types="deployment"><span>⇣</span><span>Deployment</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="manifestRel" data-types="deployment"><span>⊳</span><span>Manifestation</span></button>
</div>
</div>

<div class="tool-group collapsed" data-group="cls-elements" data-diagram-types="class">
<div class="tool-group-header"><span>🗂️</span><span style="flex:1">المان‌های Class</span><span class="tool-group-arrow">▼</span></div>
<div class="tool-group-content">
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="class" data-types="class"><span>🗂️</span><span>Class</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="interfaceClass" data-types="class"><span>◯</span><span>Interface</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="abstractClass" data-types="class"><span>🗂️</span><span>Abstract Class</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="enumeration" data-types="class"><span>📋</span><span>Enumeration</span></button>
</div>
</div>
<div class="tool-group collapsed" data-group="cls-relations" data-diagram-types="class">
<div class="tool-group-header"><span>🔗</span><span style="flex:1">روابط Class</span><span class="tool-group-arrow">▼</span></div>
<div class="tool-group-content">
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="clsAssociation" data-types="class"><span>➖</span><span>Association</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="aggregation" data-types="class"><span>◇</span><span>Aggregation</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="composition" data-types="class"><span>◆</span><span>Composition</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="inheritance" data-types="class"><span>△</span><span>Inheritance</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="clsDependency" data-types="class"><span>⇢</span><span>Dependency</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="realization" data-types="class"><span>▷</span><span>Realization</span></button>
</div>
</div>

<div class="tool-group collapsed" data-group="seq-elements" data-diagram-types="sequence">
<div class="tool-group-header"><span>🔢</span><span style="flex:1">المان‌های Sequence</span><span class="tool-group-arrow">▼</span></div>
<div class="tool-group-content">
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="participant" data-types="sequence"><span>▭</span><span>Participant</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="activation" data-types="sequence"><span>▮</span><span>Activation</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="fragment" data-types="sequence"><span>▣</span><span>Combined Fragment</span></button>
</div>
</div>
<div class="tool-group collapsed" data-group="seq-messages" data-diagram-types="sequence">
<div class="tool-group-header"><span>➡️</span><span style="flex:1">پیام‌های Sequence</span><span class="tool-group-arrow">▼</span></div>
<div class="tool-group-content">
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="syncMsg" data-types="sequence"><span>→●</span><span>Synchronous</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="asyncMsg" data-types="sequence"><span>→○</span><span>Asynchronous</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="returnMsg" data-types="sequence"><span>⇠</span><span>Return</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="selfMsg" data-types="sequence"><span>↷</span><span>Self Message</span></button>
</div>
</div>

<div class="tool-group collapsed" data-group="sw-elements" data-diagram-types="swimlane">
<div class="tool-group-header"><span>▤</span><span style="flex:1">المان‌های Swimlane</span><span class="tool-group-arrow">▼</span></div>
<div class="tool-group-content">
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="swimlane" data-types="swimlane"><span>▤</span><span>Swimlane</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="swAction" data-types="swimlane"><span>▭</span><span>Action</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="swDecision" data-types="swimlane"><span>◇</span><span>Decision</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="swForkJoin" data-types="swimlane"><span>▬</span><span>Fork/Join</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="swInitial" data-types="swimlane"><span>⚫</span><span>Initial Node</span></button>
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="swFinal" data-types="swimlane"><span>🎯</span><span>Final Node</span></button>
</div>
</div>
<div class="tool-group collapsed" data-group="sw-flows" data-diagram-types="swimlane">
<div class="tool-group-header"><span>➡️</span><span style="flex:1">جریان‌های Swimlane</span><span class="tool-group-arrow">▼</span></div>
<div class="tool-group-content">
<button class="tool-btn w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm" data-tool="swFlow" data-types="swimlane"><span>→</span><span>Control Flow</span></button>
</div>
</div>

<div class="tool-group collapsed" data-group="display">
<div class="tool-group-header"><span>📏</span><span style="flex:1">نوع خط‌کشی بوم</span><span class="tool-group-arrow">▼</span></div>
<div class="tool-group-content">
<div class="grid-type-selector">
<div class="grid-type-option" data-grid-type="none"><span>⊘</span><span>خاموش</span></div>
<div class="grid-type-option" data-grid-type="lines"><span>▦</span><span>خط</span></div>
<div class="grid-type-option active" data-grid-type="dots"><span>⠿</span><span>نقطه</span></div>
</div>
</div>
</div>

<div class="tool-group collapsed" data-group="actions">
<div class="tool-group-header"><span>⚡</span><span style="flex:1">عملیات</span><span class="tool-group-arrow">▼</span></div>
<div class="tool-group-content">
<button id="deleteBtn" class="tool-btn tool-btn-red w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm"><span>🗑️</span><span>حذف انتخاب شده</span></button>
<button id="clearBtn" class="tool-btn tool-btn-orange w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm"><span>🧹</span><span>پاک کردن دیاگرام</span></button>
<button id="exportPngBtn" class="tool-btn tool-btn-purple w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm"><span>🖼️</span><span>خروجی PNG</span></button>
</div>
</div>

<div class="tool-group collapsed" data-group="file">
<div class="tool-group-header"><span>💾</span><span style="flex:1">فایل و پروژه</span><span class="tool-group-arrow">▼</span></div>
<div class="tool-group-content">
<button id="fileSettingsBtn" class="tool-btn tool-btn-teal w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm"><span>📋</span><span>تنظیمات فایل پروژه</span></button>
<button id="settingsBtn" class="tool-btn tool-btn-cyan w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm"><span>⚙️</span><span>تنظیمات دیاگرام</span></button>
<button id="saveBtn" class="tool-btn tool-btn-green w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm"><span>💾</span><span>ذخیره پروژه</span></button>
<button id="loadBtn" class="tool-btn tool-btn-blue w-full py-2 px-3 rounded-lg mb-1 flex items-center gap-2 text-sm"><span>📂</span><span>بارگذاری فایل</span></button>
<input type="file" id="fileInput" accept=".json" class="hidden">
</div>
</div>

<div class="themed-card text-xs" style="border-color:rgba(102,126,234,.3)">
<h4 class="font-bold mb-1" style="color:var(--text-primary)">💡 راهنما:</h4>
<ul class="space-y-1 leading-relaxed" style="color:var(--text-muted)">
<li>• <b>Ctrl+Wheel</b> زوم | <b>Space+Drag</b> Pan</li>
<li>• <b>Ctrl+Z/Y</b> Undo/Redo</li>
<li>• <b>دابل‌کلیک Class</b>: ویرایشگر</li>
<li>• <b>دابل‌کلیک Swimlane</b>: تغییر جهت</li>
</ul>
</div>
</div>
</div>

<main class="flex-1 relative overflow-hidden">
<div id="canvasWrapper" class="w-full h-full canvas-container"><canvas id="canvas" class="block"></canvas></div>
<div id="emptyState" class="empty-state absolute inset-0 hidden">
<div class="text-6xl mb-4">📊</div>
<h3 class="text-xl font-bold mb-2" style="color:var(--text-secondary)">هیچ دیاگرامی وجود ندارد</h3>
<p class="text-sm mb-4" style="color:var(--text-muted)">برای شروع، روی دکمه + کلیک کنید</p>
<button onclick="showNewDiagramModal()" class="bg-gradient-to-l from-indigo-600 to-purple-600 text-white py-2 px-6 rounded-lg text-sm font-medium">+ ایجاد اولین دیاگرام</button>
</div>
</main>
</div>

<footer class="status-bar px-4 py-1.5 text-xs no-print flex-shrink-0 z-50">
<div class="flex justify-between items-center"><div id="statusText">آماده</div><div id="diagramInfo">دیاگرام‌ها: 0</div></div>
</footer>
</div>

<!-- New Diagram Modal -->
<div id="newDiagramModal" class="modal-overlay no-print">
<div class="modal-content" style="max-width:720px">
<div class="p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-bold flex items-center gap-2"><span>📊</span> ایجاد دیاگرام جدید</h3>
<button id="closeNewDiagram" class="p-1 rounded-lg hover:opacity-70" style="color:var(--text-muted)">✕</button>
</div>
<p class="text-sm mb-4" style="color:var(--text-secondary)">نوع نمودار مورد نظر خود را انتخاب کنید:</p>
<div class="grid grid-cols-2 gap-3 mb-4" id="diagramTypeGrid"></div>
<div class="flex gap-3 pt-2">
<button id="confirmNewDiagram" class="flex-1 bg-gradient-to-l from-green-600 to-emerald-600 text-white py-2.5 px-4 rounded-lg text-sm font-medium">✓ ایجاد دیاگرام</button>
<button id="cancelNewDiagram" class="flex-1 py-2.5 px-4 rounded-lg text-sm font-medium" style="background:var(--bg-tertiary);color:var(--text-secondary)">❌ انصراف</button>
</div>
</div>
</div>
</div>

<!-- Properties Panel -->
<div id="propertiesOverlay" class="properties-overlay no-print">
<div class="panel-shadow rounded-t-2xl border-t border-r border-l" style="border-color:var(--border-color)">
<div class="flex justify-center pt-2 pb-1"><div class="w-10 h-1 rounded-full" style="background:var(--border-color)"></div></div>
<div class="px-5 pb-4">
<div class="flex items-center justify-between mb-3">
<h3 class="font-bold text-sm flex items-center gap-2" style="color:var(--text-primary)"><span>⚙️</span> ویژگی‌های المان</h3>
<button id="closeProperties" class="p-1 rounded-lg hover:opacity-70" style="color:var(--text-muted)">✕</button>
</div>
<div class="grid grid-cols-3 gap-3">
<div><label class="block text-xs mb-1" style="color:var(--text-muted)">نام</label><input type="text" id="elementName" class="themed-input"></div>
<div><label class="block text-xs mb-1" style="color:var(--text-muted)">نوع</label><input type="text" id="elementType" class="themed-input-readonly" readonly></div>
<div><label class="block text-xs mb-1" style="color:var(--text-muted)">موقعیت</label><input type="text" id="elementPosition" class="themed-input-readonly" readonly></div>
</div>
<div id="swimlaneColorSection" style="display:none" class="mt-3 pt-3 border-t" style="border-color:var(--border-color)">
<label class="block text-xs mb-1" style="color:var(--text-muted)">رنگ Swimlane</label>
<input type="color" id="swimlaneColorPicker" class="w-full h-10 rounded cursor-pointer">
</div>
</div>
</div>
</div>

<!-- Diagram Settings Modal -->
<div id="settingsModal" class="modal-overlay no-print"><div class="modal-content"><div class="p-6">
<div class="flex items-center justify-between mb-4"><h3 class="text-lg font-bold"><span>⚙️</span> تنظیمات دیاگرام</h3><button id="closeSettings" class="p-1 rounded-lg hover:opacity-70" style="color:var(--text-muted)">✕</button></div>
<div class="space-y-4">
<div><label class="block text-sm font-medium mb-2">شناسه</label><input type="text" id="diagramId" class="themed-input-readonly" readonly></div>
<div><label class="block text-sm font-medium mb-2">نوع نمودار</label><input type="text" id="diagramType" class="themed-input-readonly" readonly></div>
<div><label class="block text-sm font-medium mb-2">عنوان <span class="text-red-500">*</span></label><input type="text" id="diagramTitle" class="themed-input"></div>
<div><label class="block text-sm font-medium mb-2">توضیحات</label><textarea id="diagramDescription" rows="3" class="themed-input resize-none"></textarea></div>
<div class="flex gap-3 pt-2"><button id="saveSettings" class="flex-1 bg-gradient-to-l from-indigo-600 to-purple-600 text-white py-2 px-4 rounded-lg text-sm font-medium">💾 ذخیره</button><button id="cancelSettings" class="flex-1 py-2 px-4 rounded-lg text-sm font-medium" style="background:var(--bg-tertiary);color:var(--text-secondary)">❌ انصراف</button></div>
</div>
</div></div></div>

<!-- File Settings Modal -->
<div id="fileSettingsModal" class="modal-overlay no-print"><div class="modal-content"><div class="p-6">
<div class="flex items-center justify-between mb-4"><h3 class="text-lg font-bold"><span>📋</span> تنظیمات فایل پروژه</h3><button id="closeFileSettings" class="p-1 rounded-lg hover:opacity-70" style="color:var(--text-muted)">✕</button></div>
<div class="space-y-4">
<div><label class="block text-sm font-medium mb-2">عنوان پروژه <span class="text-red-500">*</span></label><input type="text" id="fileProjectTitle" class="themed-input"></div>
<div><label class="block text-sm font-medium mb-2">توضیحات</label><textarea id="fileProjectDescription" rows="3" class="themed-input resize-none"></textarea></div>
<div class="grid grid-cols-2 gap-3"><div><label class="block text-sm font-medium mb-2">نویسنده</label><input type="text" id="fileProjectAuthor" class="themed-input"></div><div><label class="block text-sm font-medium mb-2">سازمان</label><input type="text" id="fileProjectOrganization" class="themed-input"></div></div>
<div class="themed-card text-xs space-y-1"><div class="flex justify-between"><span style="color:var(--text-muted)">تعداد:</span><span id="fileProjectDiagramCount" class="font-bold">0</span></div><div class="flex justify-between"><span style="color:var(--text-muted)">ایجاد:</span><span id="fileProjectCreatedAt" class="font-bold">-</span></div><div class="flex justify-between"><span style="color:var(--text-muted)">ویرایش:</span><span id="fileProjectUpdatedAt" class="font-bold">-</span></div></div>
<div class="flex gap-3 pt-2"><button id="saveFileSettings" class="flex-1 bg-gradient-to-l from-teal-600 to-cyan-600 text-white py-2 px-4 rounded-lg text-sm font-medium">💾 ذخیره</button><button id="cancelFileSettings" class="flex-1 py-2 px-4 rounded-lg text-sm font-medium" style="background:var(--bg-tertiary);color:var(--text-secondary)">❌ انصراف</button></div>
</div>
</div></div></div>

<!-- Save Modal -->
<div id="saveModal" class="modal-overlay no-print"><div class="modal-content save-modal-content"><div class="p-6">
<div class="flex items-center justify-between mb-4"><h3 class="text-lg font-bold"><span>💾</span> ذخیره فایل پروژه</h3><button id="closeSaveModal" class="p-1 rounded-lg hover:opacity-70" style="color:var(--text-muted)">✕</button></div>
<div class="space-y-4">
<div><label class="block text-sm font-medium mb-2">نام فایل <span class="text-red-500">*</span></label><div class="relative"><input type="text" id="saveFileName" class="themed-input pl-14" dir="ltr" style="font-family:monospace"><span class="absolute left-3 top-1/2 -translate-y-1/2 text-xs px-2 py-0.5 rounded" style="color:var(--text-muted);background:var(--bg-tertiary);font-family:monospace">.json</span></div></div>
<div class="file-preview"><div class="w-11 h-11 rounded-xl flex items-center justify-center text-2xl flex-shrink-0" style="background:linear-gradient(135deg,#667eea,#764ba2)">📄</div><div class="flex-1 min-w-0"><div class="text-sm font-semibold truncate" style="font-family:monospace;direction:ltr;text-align:left" id="saveFilePreviewName">-</div><div class="text-xs mt-1" style="color:var(--text-muted)" id="saveFilePreviewMeta">-</div></div></div>
<div class="themed-card text-xs space-y-1.5"><div class="flex justify-between"><span style="color:var(--text-muted)">📊 تعداد:</span><span id="saveDiagramCount" class="font-bold">0</span></div><div class="flex justify-between"><span style="color:var(--text-muted)">📦 حجم:</span><span id="saveFileSize" class="font-bold">-</span></div></div>
<div class="flex gap-3 pt-2"><button id="confirmSaveBtn" class="flex-1 bg-gradient-to-l from-green-600 to-emerald-600 text-white py-2.5 px-4 rounded-lg text-sm font-medium">💾 ذخیره</button><button id="cancelSaveBtn" class="flex-1 py-2.5 px-4 rounded-lg text-sm font-medium" style="background:var(--bg-tertiary);color:var(--text-secondary)">❌ انصراف</button></div>
</div>
</div></div></div>

<!-- Export PNG Modal -->
<div id="exportPngModal" class="modal-overlay no-print"><div class="modal-content save-modal-content"><div class="p-6">
<div class="flex items-center justify-between mb-4"><h3 class="text-lg font-bold"><span>🖼️</span> خروجی PNG</h3><button id="closeExportPng" class="p-1 rounded-lg hover:opacity-70" style="color:var(--text-muted)">✕</button></div>
<div class="space-y-4">
<div><label class="block text-sm font-medium mb-2">رنگ پس‌زمینه</label><div class="flex items-center gap-3"><input type="color" id="pngBgColor" value="#ffffff" class="w-12 h-10 rounded-lg cursor-pointer border-0 p-0" style="background:transparent"><input type="text" id="pngBgColorText" value="#ffffff" class="themed-input flex-1" dir="ltr" style="font-family:monospace"><button id="pngBgTransparent" class="py-2 px-3 rounded-lg text-xs font-medium" style="background:var(--bg-tertiary);color:var(--text-secondary);border:1px solid var(--border-color)">شفاف</button></div></div>
<div id="pngBgPreview" class="w-full h-16 rounded-lg border-2 flex items-center justify-center text-sm font-medium" style="border-color:var(--border-color);background:#fff;color:#333">پیش‌نمایش</div>
<div class="flex gap-3 pt-2"><button id="confirmExportPng" class="flex-1 bg-gradient-to-l from-purple-600 to-pink-600 text-white py-2.5 px-4 rounded-lg text-sm font-medium">🖼️ دانلود</button><button id="cancelExportPng" class="flex-1 py-2.5 px-4 rounded-lg text-sm font-medium" style="background:var(--bg-tertiary);color:var(--text-secondary)">❌ انصراف</button></div>
</div>
</div></div></div>

<!-- Class Editor Modal -->
<div id="classEditorModal" class="modal-overlay no-print">
<div class="modal-content class-editor-modal">
<div class="p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-bold flex items-center gap-2"><span>🗂️</span> ویرایشگر Class</h3>
<button id="closeClassEditor" class="p-1 rounded-lg hover:opacity-70" style="color:var(--text-muted)">✕</button>
</div>
<div class="mb-4">
<label class="block text-sm font-medium mb-2">نام Class</label>
<input type="text" id="classEditorName" class="themed-input">
</div>
<div class="class-editor-tabs">
<div class="class-editor-tab active" data-tab="attributes">📋 Attributes</div>
<div class="class-editor-tab" data-tab="methods">⚙️ Methods</div>
</div>
<div id="attributesTab" class="class-editor-panel">
<div class="flex justify-between items-center mb-2">
<span class="text-sm font-medium" style="color:var(--text-secondary)">ویژگی‌ها</span>
<button id="addAttrBtn" class="member-btn member-btn-add">+ افزودن</button>
</div>
<table class="member-table">
<thead><tr><th style="width:60px">Visibility</th><th>نام</th><th style="width:100px">نوع</th><th style="width:50px">Static</th><th style="width:40px"></th></tr></thead>
<tbody id="attributesTableBody"></tbody>
</table>
</div>
<div id="methodsTab" class="class-editor-panel" style="display:none">
<div class="flex justify-between items-center mb-2">
<span class="text-sm font-medium" style="color:var(--text-secondary)">متدها</span>
<button id="addMethodBtn" class="member-btn member-btn-add">+ افزودن</button>
</div>
<table class="member-table">
<thead><tr><th style="width:60px">Visibility</th><th>نام</th><th style="width:120px">پارامترها</th><th style="width:80px">نوع بازگشت</th><th style="width:50px">Static</th><th style="width:40px"></th></tr></thead>
<tbody id="methodsTableBody"></tbody>
</table>
</div>
<div class="flex gap-3 pt-4 mt-4" style="border-top:1px solid var(--border-color)">
<button id="saveClassEditor" class="flex-1 bg-gradient-to-l from-indigo-600 to-purple-600 text-white py-2 px-4 rounded-lg text-sm font-medium">💾 ذخیره تغییرات</button>
<button id="cancelClassEditor" class="flex-1 py-2 px-4 rounded-lg text-sm font-medium" style="background:var(--bg-tertiary);color:var(--text-secondary)">❌ انصراف</button>
</div>
</div>
</div>
</div>

<script>
// ===== DIAGRAM TYPES REGISTRY =====
const DIAGRAM_TYPES = {
    usecase: { id:'usecase', name:'نمودار مورد کاربرد (Use Case)', nameShort:'Use Case', icon:'⭕', available:true,
        tools:['select','actor','useCase','boundary','association','include','extend','generalization'],
        groups:['selection','uc-elements','uc-relations','display','actions','file'] },
    statemachine: { id:'statemachine', name:'نمودار ماشین حالت (State Machine)', nameShort:'State Machine', icon:'🔄', available:true,
        tools:['select','initialState','state','finalState','compositeState','transition'],
        groups:['selection','sm-elements','sm-relations','display','actions','file'] },
    activity: { id:'activity', name:'نمودار فعالیت (Activity Diagram)', nameShort:'Activity', icon:'🔀', available:true,
        tools:['select','initialNode','finalNode','action','decision','merge','forkJoin','controlFlow','objectFlow'],
        groups:['selection','act-elements','act-flows','display','actions','file'] },
    component: { id:'component', name:'نمودار کامپوننت (Component Diagram)', nameShort:'Component', icon:'🧩', available:true,
        tools:['select','component','interfaceProv','interfaceReq','package','dependency','connector','compAssociation'],
        groups:['selection','comp-elements','comp-relations','display','actions','file'] },
    deployment: { id:'deployment', name:'نمودار استقرار (Deployment Diagram)', nameShort:'Deployment', icon:'🖥️', available:true,
        tools:['select','node','device','execEnv','artifact','commPath','deployRel','manifestRel'],
        groups:['selection','dep-elements','dep-relations','display','actions','file'] },
    class: { id:'class', name:'نمودار کلاس (Class Diagram)', nameShort:'Class', icon:'🗂️', available:true,
        tools:['select','class','interfaceClass','abstractClass','enumeration','clsAssociation','aggregation','composition','inheritance','clsDependency','realization'],
        groups:['selection','cls-elements','cls-relations','display','actions','file'] },
    sequence: { id:'sequence', name:'نمودار توالی (Sequence Diagram)', nameShort:'Sequence', icon:'🔢', available:true,
        tools:['select','participant','activation','fragment','syncMsg','asyncMsg','returnMsg','selfMsg'],
        groups:['selection','seq-elements','seq-messages','display','actions','file'] },
    swimlane: { id:'swimlane', name:'نمودار Swimlane', nameShort:'Swimlane', icon:'▤', available:true,
        tools:['select','swimlane','swAction','swDecision','swForkJoin','swInitial','swFinal','swFlow'],
        groups:['selection','sw-elements','sw-flows','display','actions','file'] }
};

// ===== STATE =====
const state = {
    diagrams: [], activeDiagramId: null, currentTool: 'select',
    selectedElement: null, isDragging: false, dragStart: null,
    isDrawingRelationship: false, relationshipStart: null, relationshipStartId: null,
    mousePos: { x:0, y:0 }, sidebarVisible: true, gridType: 'dots', darkMode: true,
    collapsedGroups: new Set(), newDiagramSelectedType: 'usecase',
    editingClassId: null, resizingSwimlane: null, resizeHandle: null,
    // ZOOM & PAN
    zoom: 1.0, panX: 0, panY: 0, isPanning: false, panStart: null, spacePressed: false,
    fileMetadata: { title:'', description:'', author:'هادی خزاعی اصل', organization:'رایان پرداز کاوش', createdAt:new Date().toISOString(), updatedAt:new Date().toISOString() }
};

// ===== UTILITIES =====
function genId(){ return 'diagram-'+Date.now()+'-'+Math.random().toString(36).substr(2,9); }
function genElId(d){ return d.nextId++; }
function genTS(){ const n=new Date(); return `${n.getFullYear()}${String(n.getMonth()+1).padStart(2,'0')}${String(n.getDate()).padStart(2,'0')}-${String(n.getHours()).padStart(2,'0')}${String(n.getMinutes()).padStart(2,'0')}`; }
function sanitize(n){ return n.replace(/[\\/:*?"<>|]/g,'_').replace(/\s+/g,' ').trim().substring(0,100); }
function fmtDate(iso){ if(!iso) return '-'; try{ return new Date(iso).toLocaleDateString('fa-IR',{year:'numeric',month:'long',day:'numeric',hour:'2-digit',minute:'2-digit'}); }catch{return '-';} }
function fmtSize(b){ if(b<1024) return b+' B'; if(b<1048576) return (b/1024).toFixed(1)+' KB'; return (b/1048576).toFixed(2)+' MB'; }
function getActive(){ return state.diagrams.find(d=>d.id===state.activeDiagramId)||null; }

// ===== HISTORY (UNDO/REDO) =====
const MAX_HISTORY = 50;

function getDiagramData(d){
    // Extract only the data fields (not history/metadata)
    return {
        actors:d.actors||[], useCases:d.useCases||[], systemBoundary:d.systemBoundary||null,
        states:d.states||[], transitions:d.transitions||[],
        nodes:d.nodes||[], flows:d.flows||[],
        components:d.components||[], interfaces:d.interfaces||[], packages:d.packages||[], compRelations:d.compRelations||[],
        deployNodes:d.deployNodes||[], artifacts:d.artifacts||[], deployRelations:d.deployRelations||[],
        classes:d.classes||[], classRelations:d.classRelations||[],
        participants:d.participants||[], activations:d.activations||[], messages:d.messages||[], fragments:d.fragments||[],
        swimlanes:d.swimlanes||[], swNodes:d.swNodes||[], swFlows:d.swFlows||[],
        relationships:d.relationships||[], nextId:d.nextId
    };
}

function pushHistory(d){
    if(!d) return;
    if(!d._history) d._history = [];
    if(!d._historyIndex && d._historyIndex !== 0) d._historyIndex = -1;
    
    // Remove any future states (if we undid some)
    d._history = d._history.slice(0, d._historyIndex + 1);
    
    // Push current state
    d._history.push(JSON.stringify(getDiagramData(d)));
    
    // Limit history size
    if(d._history.length > MAX_HISTORY){
        d._history.shift();
    } else {
        d._historyIndex++;
    }
}

function restoreFromHistory(d, snapshot){
    const data = JSON.parse(snapshot);
    Object.assign(d, data);
}

function canUndo(){
    const d = getActive();
    return d && d._history && d._historyIndex > 0;
}

function canRedo(){
    const d = getActive();
    return d && d._history && d._historyIndex < d._history.length - 1;
}

function undo(){
    const d = getActive();
    if(!canUndo()) return;
    d._historyIndex--;
    restoreFromHistory(d, d._history[d._historyIndex]);
    state.selectedElement = null;
    hideProps();
    draw();
    updateStatus('↶ بازگشت');
}

function redo(){
    const d = getActive();
    if(!canRedo()) return;
    d._historyIndex++;
    restoreFromHistory(d, d._history[d._historyIndex]);
    state.selectedElement = null;
    hideProps();
    draw();
    updateStatus('↷ از نو');
}

// ===== THEME =====
function applyTheme(){ document.body.classList.toggle('dark',state.darkMode); document.getElementById('themeToggleBtn').textContent=state.darkMode?'☀️':'🌙'; draw(); }
document.getElementById('themeToggleBtn').addEventListener('click',()=>{ state.darkMode=!state.darkMode; applyTheme(); });

// ===== GRID =====
document.querySelectorAll('.grid-type-option').forEach(o=>o.addEventListener('click',()=>{ state.gridType=o.dataset.gridType; document.querySelectorAll('.grid-type-option').forEach(x=>x.classList.toggle('active',x===o)); draw(); }));
function setGridUI(t){ document.querySelectorAll('.grid-type-option').forEach(o=>o.classList.toggle('active',o.dataset.gridType===t)); }

// ===== COLLAPSIBLE GROUPS =====
document.querySelectorAll('.tool-group-header').forEach(h=>h.addEventListener('click',()=>{ const g=h.parentElement; const id=g.dataset.group; if(state.collapsedGroups.has(id)){state.collapsedGroups.delete(id);g.classList.remove('collapsed');}else{state.collapsedGroups.add(id);g.classList.add('collapsed');} }));

// ===== TOOL VISIBILITY =====
function updateToolVisibility(){
    const d = getActive();
    const allGroups = document.querySelectorAll('.tool-group');
    const allBtns = document.querySelectorAll('.tool-btn[data-tool]');
    if(!d){
        // Hide all diagram-specific groups, show only selection/display/actions/file
        allGroups.forEach(g=>{
            const dt=g.dataset.diagramTypes;
            if(dt) g.style.display='none';
            else g.style.display='';
        });
        allBtns.forEach(b=>{ b.style.display = b.dataset.tool==='select'?'':'none'; });
        return;
    }
    const dt = DIAGRAM_TYPES[d.diagramType];
    if(!dt) return;
    const allowedGroups = dt.groups;
    allGroups.forEach(g=>{
        const req = g.dataset.diagramTypes;
        if(!req){ g.style.display=''; return; }
        g.style.display = allowedGroups.includes(g.dataset.group)?'':'none';
    });
    const allowedTools = dt.tools;
    allBtns.forEach(b=>{
        if(b.dataset.tool==='select'){ b.style.display=''; return; }
        const req = b.dataset.types;
        if(!req||req==='all'){ b.style.display=''; return; }
        const types = req.split(',');
        b.style.display = types.includes(d.diagramType)?'':'none';
        if(b.style.display==='none' && state.currentTool===b.dataset.tool) setCurrentTool('select');
    });
}

// ===== ZOOM & PAN =====
const MIN_ZOOM = 0.25;
const MAX_ZOOM = 4.0;

function setZoom(newZoom, centerX, centerY){
    const oldZoom = state.zoom;
    newZoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, newZoom));
    
    if(centerX !== undefined && centerY !== undefined){
        // Zoom towards center point
        const worldX = (centerX - state.panX) / oldZoom;
        const worldY = (centerY - state.panY) / oldZoom;
        state.panX = centerX - worldX * newZoom;
        state.panY = centerY - worldY * newZoom;
    }
    
    state.zoom = newZoom;
    updateZoomDisplay();
    draw();
}

function resetZoom(){
    state.zoom = 1.0;
    state.panX = 0;
    state.panY = 0;
    updateZoomDisplay();
    draw();
}

function updateZoomDisplay(){
    document.getElementById('zoomDisplay').textContent = Math.round(state.zoom * 100) + '%';
}

document.getElementById('zoomInBtn').addEventListener('click', ()=>{
    const rect = canvas.getBoundingClientRect();
    setZoom(state.zoom * 1.2, rect.width/2, rect.height/2);
});
document.getElementById('zoomOutBtn').addEventListener('click', ()=>{
    const rect = canvas.getBoundingClientRect();
    setZoom(state.zoom / 1.2, rect.width/2, rect.height/2);
});
document.getElementById('zoomDisplay').addEventListener('click', resetZoom);

// ===== NEW DIAGRAM MODAL =====
function buildDiagramTypeGrid(){
    const grid = document.getElementById('diagramTypeGrid');
    grid.innerHTML = '';
    Object.values(DIAGRAM_TYPES).forEach(dt=>{
        const card = document.createElement('div');
        card.className = 'diagram-type-card' + (dt.available?'':' disabled') + (dt.id===state.newDiagramSelectedType&&dt.available?' selected':'');
        card.dataset.diagramType = dt.id;
        card.innerHTML = `<div class="flex items-start gap-3">
            <div class="diagram-type-icon">${dt.icon}</div>
            <div class="flex-1">
                <h4 class="font-bold text-sm mb-1" style="color:var(--text-primary)">${dt.name}</h4>
                ${dt.available?'':'<span style="display:inline-block;font-size:9px;padding:2px 6px;border-radius:4px;background:var(--bg-tertiary);color:var(--text-muted);font-weight:600;margin-top:6px">به‌زودی</span>'}
            </div>
            ${dt.available&&dt.id===state.newDiagramSelectedType?'<div style="color:#667eea">✓</div>':''}
        </div>`;
        if(dt.available) card.addEventListener('click',()=>{ state.newDiagramSelectedType=dt.id; buildDiagramTypeGrid(); });
        grid.appendChild(card);
    });
}

function showNewDiagramModal(){ state.newDiagramSelectedType='usecase'; buildDiagramTypeGrid(); document.getElementById('newDiagramModal').classList.add('visible'); }
function hideNewDiagramModal(){ document.getElementById('newDiagramModal').classList.remove('visible'); }
document.getElementById('newDiagramBtn').addEventListener('click', showNewDiagramModal);
document.getElementById('closeNewDiagram').addEventListener('click', hideNewDiagramModal);
document.getElementById('cancelNewDiagram').addEventListener('click', hideNewDiagramModal);
document.getElementById('newDiagramModal').addEventListener('click', e=>{ if(e.target.id==='newDiagramModal') hideNewDiagramModal(); });
document.getElementById('confirmNewDiagram').addEventListener('click',()=>{
    const dt=DIAGRAM_TYPES[state.newDiagramSelectedType];
    if(!dt||!dt.available){ alert('این نوع نمودار هنوز پشتیبانی نمی‌شود'); return; }
    hideNewDiagramModal(); createNewDiagram(state.newDiagramSelectedType);
});

// ===== DIAGRAM MANAGEMENT =====
function createNewDiagram(type='usecase'){
    const dt=DIAGRAM_TYPES[type]; if(!dt) return;
    const count=state.diagrams.filter(d=>d.diagramType===type).length+1;
    const nd = {
        id:genId(), diagramType:type, title:`${dt.nameShort} جدید ${count}`, description:'',
        createdAt:new Date().toISOString(), updatedAt:new Date().toISOString(),
        author:state.fileMetadata.author, version:'1.0', nextId:1,
        actors:[], useCases:[], systemBoundary:null,
        states:[], transitions:[],
        nodes:[], flows:[],
        components:[], interfaces:[], packages:[], compRelations:[],
        deployNodes:[], artifacts:[], deployRelations:[],
        classes:[], classRelations:[],
        participants:[], activations:[], messages:[], fragments:[],
        swimlanes:[], swNodes:[], swFlows:[],
        relationships:[],
        _history: [], _historyIndex: -1
    };
    state.diagrams.push(nd);
    pushHistory(nd); // Initial state
    state.fileMetadata.updatedAt=new Date().toISOString();
    switchDiagram(nd.id); renderTabs(); updateInfo();
    updateStatus(`✅ ${dt.nameShort}: "${nd.title}" ایجاد شد`);
}

function deleteDiagram(id){
    const d=state.diagrams.find(x=>x.id===id); if(!d) return;
    if(!confirm(`حذف "${d.title}"؟`)) return;
    state.diagrams=state.diagrams.filter(x=>x.id!==id);
    if(state.activeDiagramId===id){
        if(state.diagrams.length>0) switchDiagram(state.diagrams[0].id);
        else{
            state.activeDiagramId=null;
            state.selectedElement=null;
            hideProps();
            // Reset zoom/pan
            state.zoom = 1.0;
            state.panX = 0;
            state.panY = 0;
            updateZoomDisplay();
            updateToolVisibility(); // Hide diagram-specific tools
        }
    }
    renderTabs(); updateInfo(); updateVis(); updateStatus('🗑️ حذف شد');
}

function switchDiagram(id){
    if(state.activeDiagramId===id) return;
    state.activeDiagramId=id; state.selectedElement=null; state.currentTool='select';
    state.isDragging=false; state.isDrawingRelationship=false;
    // Reset zoom/pan when switching diagrams
    state.zoom = 1.0;
    state.panX = 0;
    state.panY = 0;
    updateZoomDisplay();
    setActiveUI('select'); hideProps(); renderTabs(); updateVis(); updateToolVisibility();
    const d=getActive(); if(d){ const dt=DIAGRAM_TYPES[d.diagramType]; updateStatus(`📊 ${dt?.nameShort}: ${d.title}`); }
    scheduleResize();
}

function updateVis(){ const c=document.getElementById('canvasWrapper'),e=document.getElementById('emptyState'); if(!state.diagrams.length){c.classList.add('hidden');e.classList.remove('hidden');}else{c.classList.remove('hidden');e.classList.add('hidden');} }
function toggleSidebar(){ state.sidebarVisible=!state.sidebarVisible; document.getElementById('sidebarWrapper').classList.toggle('collapsed',!state.sidebarVisible); setTimeout(scheduleResize,320); }
document.getElementById('toggleSidebarBtn').addEventListener('click', toggleSidebar);

// Undo/Redo buttons
document.getElementById('undoBtn').addEventListener('click', undo);
document.getElementById('redoBtn').addEventListener('click', redo);

// ===== TABS =====
function renderTabs(){
    const c=document.getElementById('tabsContainer'); c.innerHTML='';
    state.diagrams.forEach(d=>{
        const dt=DIAGRAM_TYPES[d.diagramType]||DIAGRAM_TYPES.usecase;
        const tab=document.createElement('div');
        tab.className='diagram-tab'+(d.id===state.activeDiagramId?' active':'');
        tab.innerHTML=`<span style="font-size:14px">${dt.icon}</span><span class="tab-title" title="${d.title}">${d.title||'بدون عنوان'}</span><span class="tab-type-badge">${dt.nameShort}</span><span class="tab-close" title="حذف">×</span>`;
        tab.querySelector('.tab-close').addEventListener('click',e=>{e.stopPropagation();deleteDiagram(d.id);});
        tab.addEventListener('click',()=>switchDiagram(d.id));
        tab.addEventListener('dblclick',e=>{e.stopPropagation();switchDiagram(d.id);showSettings();});
        c.appendChild(tab);
    });
    const at=c.querySelector('.diagram-tab.active'); if(at) at.scrollIntoView({behavior:'smooth',block:'nearest',inline:'center'});
}

function updateInfo(){
    const a=getActive(); let info=`دیاگرام‌ها: ${state.diagrams.length}`;
    if(state.fileMetadata.title) info=`📁 ${state.fileMetadata.title} | `+info;
    if(a){ const dt=DIAGRAM_TYPES[a.diagramType]||DIAGRAM_TYPES.usecase;
        let ec=0;
        if(a.diagramType==='usecase') ec=a.actors.length+a.useCases.length+(a.systemBoundary?1:0)+a.relationships.length;
        else if(a.diagramType==='statemachine') ec=a.states.length+a.transitions.length;
        else if(a.diagramType==='activity') ec=(a.nodes||[]).length+(a.flows||[]).length;
        else if(a.diagramType==='component') ec=(a.components||[]).length+(a.interfaces||[]).length+(a.packages||[]).length+(a.compRelations||[]).length;
        else if(a.diagramType==='deployment') ec=(a.deployNodes||[]).length+(a.artifacts||[]).length+(a.deployRelations||[]).length;
        else if(a.diagramType==='class') ec=(a.classes||[]).length+(a.classRelations||[]).length;
        else if(a.diagramType==='sequence') ec=(a.participants||[]).length+(a.activations||[]).length+(a.messages||[]).length+(a.fragments||[]).length;
        else if(a.diagramType==='swimlane') ec=(a.swimlanes||[]).length+(a.swNodes||[]).length+(a.swFlows||[]).length;
        info+=` | ${dt.nameShort}: ${a.title} | المان‌ها: ${ec}`;
    }
    document.getElementById('diagramInfo').textContent=info;
}
function updateStatus(t){ document.getElementById('statusText').textContent=t; }

// ===== CANVAS =====
const canvas=document.getElementById('canvas'), ctx=canvas.getContext('2d'), cw=document.getElementById('canvasWrapper');
function resizeCanvas(){ if(cw.classList.contains('hidden')||cw.offsetParent===null) return false; const w=cw.clientWidth,h=cw.clientHeight; if(w<=0||h<=0) return false; if(canvas.width!==w||canvas.height!==h){canvas.width=w;canvas.height=h;} draw(); return true; }
function scheduleResize(){ requestAnimationFrame(()=>requestAnimationFrame(()=>{ if(!resizeCanvas()) setTimeout(resizeCanvas,50); })); }
window.addEventListener('resize', scheduleResize);

// ===== PROPERTIES =====
const propsOvl=document.getElementById('propertiesOverlay');
function showProps(){ propsOvl.classList.add('visible'); }
function hideProps(){ propsOvl.classList.remove('visible'); }
document.getElementById('closeProperties').addEventListener('click',()=>{ state.selectedElement=null; hideProps(); draw(); });
function updatePropsPanel(){
    if(!state.selectedElement){ hideProps(); return; }
    showProps();
    document.getElementById('elementName').value=state.selectedElement.name||'';
    const tn={actor:'Actor',useCase:'Use Case',systemBoundary:'System Boundary',initialState:'Initial State',state:'State',finalState:'Final State',compositeState:'Composite State',initialNode:'Initial Node',finalNode:'Final Node',action:'Action',decision:'Decision',merge:'Merge',forkJoin:'Fork/Join',component:'Component',interfaceProv:'Interface Provided',interfaceReq:'Interface Required',package:'Package',node:'Node',device:'Device',execEnv:'Execution Env',artifact:'Artifact',class:'Class',interfaceClass:'Interface',abstractClass:'Abstract Class',enumeration:'Enumeration',participant:'Participant',activation:'Activation',fragment:'Combined Fragment',swimlane:'Swimlane',swAction:'Action',swDecision:'Decision',swForkJoin:'Fork/Join',swInitial:'Initial Node',swFinal:'Final Node'};
    document.getElementById('elementType').value=tn[state.selectedElement.elType]||state.selectedElement.elType;
    document.getElementById('elementPosition').value=state.selectedElement.x!==undefined?`${Math.round(state.selectedElement.x)}, ${Math.round(state.selectedElement.y)}`:'N/A';
    
    const colorSection = document.getElementById('swimlaneColorSection');
    if(state.selectedElement.elType==='swimlane'){
        colorSection.style.display='block';
        document.getElementById('swimlaneColorPicker').value=state.selectedElement.color||'#ebf8ff';
    } else {
        colorSection.style.display='none';
    }
}

document.getElementById('swimlaneColorPicker').addEventListener('input',e=>{
    if(!state.selectedElement||state.selectedElement.elType!=='swimlane') return;
    const d=getActive(); if(!d) return;
    const i=(d.swimlanes||[]).findIndex(s=>s.id===state.selectedElement.id);
    if(i>=0){ d.swimlanes[i].color=e.target.value; draw(); }
});

// ===== TOOL SELECTION =====
const toolNames={select:'انتخاب',actor:'Actor',useCase:'Use Case',boundary:'Boundary',association:'Association',include:'Include',extend:'Extend',generalization:'Generalization',initialState:'Initial State',state:'State',finalState:'Final State',compositeState:'Composite State',transition:'Transition',initialNode:'Initial Node',finalNode:'Final Node',action:'Action',decision:'Decision',merge:'Merge',forkJoin:'Fork/Join',controlFlow:'Control Flow',objectFlow:'Object Flow',component:'Component',interfaceProv:'Interface Provided',interfaceReq:'Interface Required',package:'Package',dependency:'Dependency',connector:'Connector',compAssociation:'Association',node:'Node',device:'Device',execEnv:'Execution Env',artifact:'Artifact',commPath:'Communication Path',deployRel:'Deployment',manifestRel:'Manifestation',class:'Class',interfaceClass:'Interface',abstractClass:'Abstract Class',enumeration:'Enumeration',clsAssociation:'Association',aggregation:'Aggregation',composition:'Composition',inheritance:'Inheritance',clsDependency:'Dependency',realization:'Realization',participant:'Participant',activation:'Activation',fragment:'Fragment',syncMsg:'Synchronous',asyncMsg:'Asynchronous',returnMsg:'Return',selfMsg:'Self Message',swimlane:'Swimlane',swAction:'Action',swDecision:'Decision',swForkJoin:'Fork/Join',swInitial:'Initial Node',swFinal:'Final Node',swFlow:'Control Flow'};
function setActiveUI(t){ document.querySelectorAll('.tool-btn').forEach(b=>b.classList.remove('active')); const b=document.getElementById(t+'Tool'); if(b) b.classList.add('active'); }
function setCurrentTool(t){ state.currentTool=t; setActiveUI(t); updateStatus(`آماده | ابزار: ${toolNames[t]||t}`); }
function switchToSelect(){ setCurrentTool('select'); }
document.querySelectorAll('.tool-btn[data-tool]').forEach(b=>b.addEventListener('click',()=>setCurrentTool(b.dataset.tool)));

// ===== MODALS =====
const settingsModal=document.getElementById('settingsModal');
function showSettings(){ const d=getActive(); if(!d){alert('دیاگرامی نیست');return;} const dt=DIAGRAM_TYPES[d.diagramType]||DIAGRAM_TYPES.usecase; document.getElementById('diagramId').value=d.id; document.getElementById('diagramType').value=dt.name; document.getElementById('diagramTitle').value=d.title; document.getElementById('diagramDescription').value=d.description; settingsModal.classList.add('visible'); }
function hideSettings(){ settingsModal.classList.remove('visible'); }
document.getElementById('settingsBtn').addEventListener('click',showSettings);
document.getElementById('closeSettings').addEventListener('click',hideSettings);
document.getElementById('cancelSettings').addEventListener('click',hideSettings);
document.getElementById('saveSettings').addEventListener('click',()=>{
    const d=getActive(); if(!d) return;
    const t=document.getElementById('diagramTitle').value.trim();
    if(!t){alert('عنوان را وارد کنید');return;}
    d.title=t; d.description=document.getElementById('diagramDescription').value.trim();
    d.updatedAt=new Date().toISOString();
    pushHistory(d); // Save change
    hideSettings(); renderTabs(); updateInfo(); updateStatus('✅ ذخیره شد');
});
settingsModal.addEventListener('click',e=>{if(e.target===settingsModal)hideSettings();});

const fileSettingsModal=document.getElementById('fileSettingsModal');
function showFileSettings(){ document.getElementById('fileProjectTitle').value=state.fileMetadata.title||''; document.getElementById('fileProjectDescription').value=state.fileMetadata.description||''; document.getElementById('fileProjectAuthor').value=state.fileMetadata.author||''; document.getElementById('fileProjectOrganization').value=state.fileMetadata.organization||''; document.getElementById('fileProjectDiagramCount').textContent=state.diagrams.length; document.getElementById('fileProjectCreatedAt').textContent=fmtDate(state.fileMetadata.createdAt); document.getElementById('fileProjectUpdatedAt').textContent=fmtDate(state.fileMetadata.updatedAt); fileSettingsModal.classList.add('visible'); }
function hideFileSettings(){ fileSettingsModal.classList.remove('visible'); }
document.getElementById('fileSettingsBtn').addEventListener('click',showFileSettings);
document.getElementById('closeFileSettings').addEventListener('click',hideFileSettings);
document.getElementById('cancelFileSettings').addEventListener('click',hideFileSettings);
document.getElementById('saveFileSettings').addEventListener('click',()=>{ const t=document.getElementById('fileProjectTitle').value.trim(); if(!t){alert('عنوان را وارد کنید');return;} state.fileMetadata.title=t; state.fileMetadata.description=document.getElementById('fileProjectDescription').value.trim(); state.fileMetadata.author=document.getElementById('fileProjectAuthor').value.trim()||'هادی خزاعی اصل'; state.fileMetadata.organization=document.getElementById('fileProjectOrganization').value.trim()||'رایان پرداز کاوش'; state.fileMetadata.updatedAt=new Date().toISOString(); hideFileSettings(); updateInfo(); updateStatus('✅ ذخیره شد'); });
fileSettingsModal.addEventListener('click',e=>{if(e.target===fileSettingsModal)hideFileSettings();});

const saveModal=document.getElementById('saveModal'), saveFN=document.getElementById('saveFileName');
function showSaveModal(){ if(!state.diagrams.length){alert('دیاگرامی نیست');return;} const unt=state.diagrams.filter(d=>!d.title?.trim()); if(unt.length){alert(`${unt.length} دیاگرام بدون عنوان`);return;} saveFN.value=(state.fileMetadata.title?sanitize(state.fileMetadata.title):'uml-diagrams')+'-'+genTS(); updateSavePreview(); document.getElementById('saveDiagramCount').textContent=state.diagrams.length; saveModal.classList.add('visible'); setTimeout(()=>{saveFN.focus();saveFN.select();},100); }
function hideSaveModal(){ saveModal.classList.remove('visible'); }
function buildSaveData(){
    return {
        version:'5.2', fileTitle:state.fileMetadata.title, fileDescription:state.fileMetadata.description,
        author:state.fileMetadata.author, organization:state.fileMetadata.organization,
        createdAt:state.fileMetadata.createdAt, updatedAt:new Date().toISOString(),
        preferences:{gridType:state.gridType,darkMode:state.darkMode},
        activeDiagramId:state.activeDiagramId,
        diagrams:state.diagrams.map(d=>{
            const data = getDiagramData(d);
            return {
                id:d.id, diagramType:d.diagramType, title:d.title, description:d.description,
                createdAt:d.createdAt, updatedAt:new Date().toISOString(),
                author:d.author, version:d.version,
                ...data
            };
        })
    };
}
function updateSavePreview(){ const sn=sanitize(saveFN.value.trim()); document.getElementById('saveFilePreviewName').textContent=sn?sn+'.json':'(نام وارد نشده)'; document.getElementById('saveFilePreviewMeta').textContent=`JSON • ${new Date().toLocaleString('fa-IR',{year:'numeric',month:'short',day:'numeric',hour:'2-digit',minute:'2-digit'})}`; try{document.getElementById('saveFileSize').textContent=fmtSize(new Blob([JSON.stringify(buildSaveData(),null,2)]).size);}catch{document.getElementById('saveFileSize').textContent='-';} }
saveFN.addEventListener('input',updateSavePreview);
document.getElementById('closeSaveModal').addEventListener('click',hideSaveModal);
document.getElementById('cancelSaveBtn').addEventListener('click',hideSaveModal);
saveModal.addEventListener('click',e=>{if(e.target===saveModal)hideSaveModal();});
document.getElementById('confirmSaveBtn').addEventListener('click',()=>{ const sn=sanitize(saveFN.value.trim()); if(!sn){alert('نام معتبر');saveFN.focus();return;} const b=new Blob([JSON.stringify(buildSaveData(),null,2)],{type:'application/json'}); const a=document.createElement('a'); a.href=URL.createObjectURL(b); a.download=sn+'.json'; a.click(); URL.revokeObjectURL(a.href); hideSaveModal(); updateStatus(`✅ "${sn}.json" ذخیره شد`); });
document.getElementById('saveBtn').addEventListener('click',showSaveModal);

// Export PNG
const expModal=document.getElementById('exportPngModal'), pngC=document.getElementById('pngBgColor'), pngCT=document.getElementById('pngBgColorText'), pngP=document.getElementById('pngBgPreview');
let pngTrans=false;
function updPng(){ const c=pngC.value; pngCT.value=c; pngP.style.background=c; const r=parseInt(c.substr(1,2),16),g=parseInt(c.substr(3,2),16),b=parseInt(c.substr(5,2),16); pngP.style.color=(0.299*r+0.587*g+0.114*b)/255>0.5?'#333':'#eee'; pngTrans=false; }
pngC.addEventListener('input',updPng);
pngCT.addEventListener('input',()=>{ if(/^#[0-9a-fA-F]{6}$/.test(pngCT.value.trim())){pngC.value=pngCT.value.trim();updPng();} });
document.getElementById('pngBgTransparent').addEventListener('click',()=>{ pngTrans=true; pngP.style.background='repeating-conic-gradient(#ccc 0% 25%,#fff 0% 50%) 0 0/16px 16px'; pngP.style.color='#666'; pngCT.value='transparent'; });
function showExpPng(){ const d=getActive(); if(!d){alert('دیاگرامی نیست');return;} pngC.value=state.darkMode?'#1a202c':'#ffffff'; pngTrans=false; updPng(); expModal.classList.add('visible'); }
function hideExpPng(){ expModal.classList.remove('visible'); }
document.getElementById('exportPngBtn').addEventListener('click',showExpPng);
document.getElementById('closeExportPng').addEventListener('click',hideExpPng);
document.getElementById('cancelExportPng').addEventListener('click',hideExpPng);
expModal.addEventListener('click',e=>{if(e.target===expModal)hideExpPng();});
document.getElementById('confirmExportPng').addEventListener('click',()=>{
    const d=getActive(); if(!d) return;
    const ts=state.selectedElement; state.selectedElement=null;
    // Export without zoom/pan (1:1 scale)
    const oc=document.createElement('canvas'); oc.width=canvas.width; oc.height=canvas.height; const ox=oc.getContext('2d');
    if(!pngTrans){ox.fillStyle=pngC.value;ox.fillRect(0,0,oc.width,oc.height);}
    const tc=getTC(); drawGrid(ox,oc.width,oc.height,tc,pngTrans);
    // Draw diagram at 1:1 scale (no zoom)
    const savedZoom = state.zoom, savedPanX = state.panX, savedPanY = state.panY;
    state.zoom = 1; state.panX = 0; state.panY = 0;
    drawDiag(ox,d,tc);
    state.zoom = savedZoom; state.panX = savedPanX; state.panY = savedPanY;
    const a=document.createElement('a'); a.download=`${d.title}-${Date.now()}.png`; a.href=oc.toDataURL('image/png'); a.click();
    state.selectedElement=ts; draw(); hideExpPng(); updateStatus('✅ PNG ذخیره شد');
});

// Load
document.getElementById('loadBtn').addEventListener('click',()=>document.getElementById('fileInput').click());
document.getElementById('fileInput').addEventListener('change',e=>{
    const f=e.target.files[0]; if(!f) return;
    if(state.diagrams.length&&!confirm('جایگزین شود؟')){e.target.value='';return;}
    const r=new FileReader(); r.onload=ev=>{
        try{
            const d=JSON.parse(ev.target.result);
            state.fileMetadata={title:d.fileTitle||d.title||'',description:d.fileDescription||d.description||'',author:d.author||'هادی خزاعی اصل',organization:d.organization||'رایان پرداز کاوش',createdAt:d.createdAt||new Date().toISOString(),updatedAt:d.updatedAt||new Date().toISOString()};
            if(d.preferences){if(d.preferences.gridType){state.gridType=d.preferences.gridType;setGridUI(state.gridType);}if(typeof d.preferences.darkMode==='boolean'){state.darkMode=d.preferences.darkMode;applyTheme();}}
            if(d.diagrams&&Array.isArray(d.diagrams)){
                state.diagrams=d.diagrams.map(di=>{
                    const loaded = {
                        ...di,
                        diagramType:di.diagramType||di.type||'usecase',
                        states:di.states||[], transitions:di.transitions||[],
                        actors:di.actors||[], useCases:di.useCases||[],
                        nodes:di.nodes||[], flows:di.flows||[],
                        components:di.components||[], interfaces:di.interfaces||[], packages:di.packages||[], compRelations:di.compRelations||[],
                        deployNodes:di.deployNodes||[], artifacts:di.artifacts||[], deployRelations:di.deployRelations||[],
                        classes:di.classes||[], classRelations:di.classRelations||[],
                        participants:di.participants||[], activations:di.activations||[], messages:di.messages||[], fragments:di.fragments||[],
                        swimlanes:di.swimlanes||[], swNodes:di.swNodes||[], swFlows:di.swFlows||[],
                        _history: [], _historyIndex: -1
                    };
                    return loaded;
                });
                state.activeDiagramId=d.activeDiagramId||state.diagrams[0]?.id||null;
                // Initialize history for loaded diagrams
                state.diagrams.forEach(di => pushHistory(di));
            } else if(d.id||d.actors){
                const loaded = {
                    id:d.id||genId(), diagramType:d.diagramType||d.type||'usecase',
                    title:d.title||'وارد شده', description:d.description||'',
                    createdAt:d.createdAt||new Date().toISOString(), updatedAt:new Date().toISOString(),
                    author:d.author||'هادی خزاعی اصل', version:'1.0',
                    actors:d.actors||[], useCases:d.useCases||[], systemBoundary:d.systemBoundary||null,
                    states:d.states||[], transitions:d.transitions||[],
                    nodes:d.nodes||[], flows:d.flows||[],
                    components:d.components||[], interfaces:d.interfaces||[], packages:d.packages||[], compRelations:d.compRelations||[],
                    deployNodes:d.deployNodes||[], artifacts:d.artifacts||[], deployRelations:d.deployRelations||[],
                    classes:d.classes||[], classRelations:d.classRelations||[],
                    participants:d.participants||[], activations:d.activations||[], messages:d.messages||[], fragments:d.fragments||[],
                    swimlanes:d.swimlanes||[], swNodes:d.swNodes||[], swFlows:d.swFlows||[],
                    _history: [], _historyIndex: -1,
                    nextId:d.nextId||1
                };
                state.diagrams=[loaded];
                state.activeDiagramId=loaded.id;
                pushHistory(loaded);
            } else throw new Error('فرمت نامعتبر');
            state.selectedElement=null; state.currentTool='select'; setActiveUI('select');
            state.zoom = 1; state.panX = 0; state.panY = 0; updateZoomDisplay();
            renderTabs(); updateInfo(); updateVis(); updatePropsPanel(); updateToolVisibility(); scheduleResize();
            updateStatus(`✅ بارگذاری شد`);
        }catch(err){alert('خطا: '+err.message);}
    }; r.readAsText(f); e.target.value='';
});

// ===== DRAWING ENGINE =====
function getTC(){
    const cs=getComputedStyle(document.body);
    return { actorStroke:cs.getPropertyValue('--actor-stroke').trim(), ucFill:cs.getPropertyValue('--uc-fill').trim(), ucStroke:cs.getPropertyValue('--uc-stroke').trim(), relStroke:cs.getPropertyValue('--rel-stroke').trim(), sbStroke:cs.getPropertyValue('--sb-stroke').trim(), labelBg:cs.getPropertyValue('--label-bg').trim(), textPrimary:cs.getPropertyValue('--text-primary').trim(), textMuted:cs.getPropertyValue('--text-muted').trim(), gridLine:cs.getPropertyValue('--grid-line').trim(), canvasBg:cs.getPropertyValue('--canvas-bg').trim(), selected:'#667eea', stateFill:cs.getPropertyValue('--state-fill').trim(), stateStroke:cs.getPropertyValue('--state-stroke').trim(), stateInit:cs.getPropertyValue('--state-init').trim(), stateFinalOuter:cs.getPropertyValue('--state-final-outer').trim(), stateFinalInner:cs.getPropertyValue('--state-final-inner').trim(), transStroke:cs.getPropertyValue('--transition-stroke').trim(), transLabel:cs.getPropertyValue('--transition-label').trim(), activityFill:cs.getPropertyValue('--activity-fill').trim(), activityStroke:cs.getPropertyValue('--activity-stroke').trim(), decisionFill:cs.getPropertyValue('--decision-fill').trim(), decisionStroke:cs.getPropertyValue('--decision-stroke').trim(), forkStroke:cs.getPropertyValue('--fork-stroke').trim(), flowStroke:cs.getPropertyValue('--flow-stroke').trim(), flowLabel:cs.getPropertyValue('--flow-label').trim(), compFill:cs.getPropertyValue('--comp-fill').trim(), compStroke:cs.getPropertyValue('--comp-stroke').trim(), compIcon:cs.getPropertyValue('--comp-icon').trim(), ifaceStroke:cs.getPropertyValue('--iface-stroke').trim(), pkgFill:cs.getPropertyValue('--pkg-fill').trim(), pkgStroke:cs.getPropertyValue('--pkg-stroke').trim(), depStroke:cs.getPropertyValue('--dep-stroke').trim(), nodeFaceTop:cs.getPropertyValue('--node-face-top').trim(), nodeFaceFront:cs.getPropertyValue('--node-face-front').trim(), nodeFaceSide:cs.getPropertyValue('--node-face-side').trim(), nodeStroke:cs.getPropertyValue('--node-stroke').trim(), artifactFill:cs.getPropertyValue('--artifact-fill').trim(), artifactStroke:cs.getPropertyValue('--artifact-stroke').trim(), classFill:cs.getPropertyValue('--class-fill').trim(), classStroke:cs.getPropertyValue('--class-stroke').trim(), classHeader:cs.getPropertyValue('--class-header').trim(), classAttr:cs.getPropertyValue('--class-attr').trim(), classMethod:cs.getPropertyValue('--class-method').trim(), seqLifeline:cs.getPropertyValue('--seq-lifeline').trim(), seqParticipantFill:cs.getPropertyValue('--seq-participant-fill').trim(), seqParticipantStroke:cs.getPropertyValue('--seq-participant-stroke').trim(), seqActivation:cs.getPropertyValue('--seq-activation').trim(), seqActivationStroke:cs.getPropertyValue('--seq-activation-stroke').trim(), seqMsg:cs.getPropertyValue('--seq-msg').trim(), seqReturn:cs.getPropertyValue('--seq-return').trim(), seqFragmentFill:cs.getPropertyValue('--seq-fragment-fill').trim(), seqFragmentStroke:cs.getPropertyValue('--seq-fragment-stroke').trim(), swimlaneDefault:cs.getPropertyValue('--swimlane-default').trim(), swimlaneStroke:cs.getPropertyValue('--swimlane-stroke').trim() };
}

function drawGrid(c,w,h,tc,trans){
    if(state.gridType==='none') return;
    const s=20;
    // Calculate visible area in world coordinates
    const startX = -state.panX / state.zoom;
    const startY = -state.panY / state.zoom;
    const endX = startX + w / state.zoom;
    const endY = startY + h / state.zoom;
    
    // Snap to grid
    const gridStartX = Math.floor(startX / s) * s;
    const gridStartY = Math.floor(startY / s) * s;
    
    if(state.gridType==='lines'){
        c.strokeStyle=trans?'rgba(128,128,128,.15)':tc.gridLine;
        c.lineWidth=.5 / state.zoom;
        c.setLineDash([]);
        for(let x=gridStartX;x<=endX;x+=s){c.beginPath();c.moveTo(x,gridStartY);c.lineTo(x,endY);c.stroke();}
        for(let y=gridStartY;y<=endY;y+=s){c.beginPath();c.moveTo(gridStartX,y);c.lineTo(endX,y);c.stroke();}
    } else if(state.gridType==='dots'){
        c.fillStyle=trans?'rgba(128,128,128,.3)':tc.gridLine;
        const r=(state.darkMode?1.2:1) / state.zoom;
        for(let x=gridStartX;x<=endX;x+=s){
            for(let y=gridStartY;y<=endY;y+=s){
                c.beginPath();c.arc(x,y,r,0,Math.PI*2);c.fill();
            }
        }
    }
}

function draw(){
    if(canvas.width<=0||canvas.height<=0) return;
    const d=getActive(), tc=getTC();
    ctx.clearRect(0,0,canvas.width,canvas.height);
    ctx.fillStyle=tc.canvasBg; ctx.fillRect(0,0,canvas.width,canvas.height);
    
    // Apply zoom & pan transform
    ctx.save();
    ctx.translate(state.panX, state.panY);
    ctx.scale(state.zoom, state.zoom);
    
    drawGrid(ctx,canvas.width,canvas.height,tc,false);
    if(d) drawDiag(ctx,d,tc);
    
    ctx.restore();
}

function drawDiag(c,d,tc){
    if(d.diagramType==='usecase') drawUseCase(c,d,tc);
    else if(d.diagramType==='statemachine') drawStateMachine(c,d,tc);
    else if(d.diagramType==='activity') drawActivity(c,d,tc);
    else if(d.diagramType==='component') drawComponent(c,d,tc);
    else if(d.diagramType==='deployment') drawDeployment(c,d,tc);
    else if(d.diagramType==='class') drawClassDiagram(c,d,tc);
    else if(d.diagramType==='sequence') drawSequenceDiagram(c,d,tc);
    else if(d.diagramType==='swimlane') drawSwimlaneDiagram(c,d,tc);

    if(state.isDrawingRelationship&&state.relationshipStart&&c===ctx){
        c.strokeStyle=tc.selected; c.lineWidth=2/state.zoom; c.setLineDash([5/state.zoom,5/state.zoom]);
        c.beginPath(); c.moveTo(state.relationshipStart.x,state.relationshipStart.y); c.lineTo(state.mousePos.x,state.mousePos.y); c.stroke(); c.setLineDash([]);
    }
    if(state.selectedElement&&c===ctx) drawSel(c,state.selectedElement,tc);
}

// ===== USE CASE =====
function drawUseCase(c,d,tc){
    if(d.systemBoundary){ const sb=d.systemBoundary, isS=state.selectedElement?.id===sb.id; c.strokeStyle=isS?tc.selected:tc.sbStroke; c.lineWidth=(isS?3:2)/state.zoom; c.setLineDash([]); c.strokeRect(sb.x,sb.y,sb.width,sb.height); c.fillStyle=tc.textPrimary; c.font=`bold ${16/state.zoom}px Vazirmatn`; c.textAlign='center'; c.textBaseline='bottom'; c.fillText(sb.name,sb.x+sb.width/2,sb.y-8/state.zoom); }
    d.relationships.forEach(r=>drawRel(c,r,d,tc));
    d.actors.forEach(a=>{ const isS=state.selectedElement?.id===a.id; c.strokeStyle=isS?tc.selected:tc.actorStroke; c.lineWidth=(isS?3:2)/state.zoom; c.beginPath();c.arc(a.x,a.y-20/state.zoom,10/state.zoom,0,Math.PI*2);c.stroke(); c.beginPath();c.moveTo(a.x,a.y-10/state.zoom);c.lineTo(a.x,a.y+15/state.zoom);c.stroke(); c.beginPath();c.moveTo(a.x-15/state.zoom,a.y);c.lineTo(a.x+15/state.zoom,a.y);c.stroke(); c.beginPath();c.moveTo(a.x,a.y+15/state.zoom);c.lineTo(a.x-12/state.zoom,a.y+30/state.zoom);c.stroke(); c.beginPath();c.moveTo(a.x,a.y+15/state.zoom);c.lineTo(a.x+12/state.zoom,a.y+30/state.zoom);c.stroke(); c.fillStyle=tc.textPrimary;c.font=`${13/state.zoom}px Vazirmatn`;c.textAlign='center';c.textBaseline='top';c.fillText(a.name,a.x,a.y+36/state.zoom); });
    d.useCases.forEach(u=>{ const isS=state.selectedElement?.id===u.id; c.fillStyle=isS?(state.darkMode?'#2a2050':'#eef2ff'):tc.ucFill; c.strokeStyle=isS?tc.selected:tc.ucStroke; c.lineWidth=(isS?3:2)/state.zoom; c.beginPath();c.ellipse(u.x,u.y,u.radius*1.5,u.radius,0,0,Math.PI*2);c.fill();c.stroke(); c.fillStyle=tc.textPrimary;c.font=`${13/state.zoom}px Vazirmatn`;c.textAlign='center';c.textBaseline='middle'; c.fillText(u.name,u.x,u.y); });
}

function drawRel(c,r,d,tc){
    const from=findEl(r.from,d), to=findEl(r.to,d); if(!from||!to) return;
    const fp=elCenter(from), tp=elCenter(to);
    c.strokeStyle=tc.relStroke; c.lineWidth=2/state.zoom; c.setLineDash([]);
    if(r.type==='association'){ c.beginPath();c.moveTo(fp.x,fp.y);c.lineTo(tp.x,tp.y);c.stroke(); }
    else if(r.type==='include'||r.type==='extend'){ c.setLineDash([6/state.zoom,4/state.zoom]);c.beginPath();c.moveTo(fp.x,fp.y);c.lineTo(tp.x,tp.y);c.stroke();c.setLineDash([]); drawArr(c,tp.x,tp.y,fp.x,fp.y,tc); const mx=(fp.x+tp.x)/2,my=(fp.y+tp.y)/2,lbl=`<<${r.type}>>`,tw=c.measureText(lbl).width+8/state.zoom; c.fillStyle=tc.labelBg;c.fillRect(mx-tw/2,my-18/state.zoom,tw,16/state.zoom); c.fillStyle=tc.textMuted;c.font=`${11/state.zoom}px Vazirmatn`;c.textAlign='center';c.textBaseline='middle';c.fillText(lbl,mx,my-10/state.zoom); }
    else if(r.type==='generalization'){ c.beginPath();c.moveTo(fp.x,fp.y);c.lineTo(tp.x,tp.y);c.stroke(); drawTri(c,tp.x,tp.y,fp.x,fp.y,tc); }
}

function drawArr(c,tx,ty,fx,fy,tc){ const a=Math.atan2(ty-fy,tx-fx),l=12/state.zoom,ang=Math.PI/7; c.strokeStyle=tc.relStroke; c.lineWidth=2/state.zoom; c.beginPath();c.moveTo(tx,ty);c.lineTo(tx-l*Math.cos(a-ang),ty-l*Math.sin(a-ang));c.moveTo(tx,ty);c.lineTo(tx-l*Math.cos(a+ang),ty-l*Math.sin(a+ang));c.stroke(); }
function drawTri(c,tx,ty,fx,fy,tc){ const a=Math.atan2(ty-fy,tx-fx),s=14/state.zoom; c.fillStyle=tc.labelBg;c.strokeStyle=tc.relStroke;c.lineWidth=2/state.zoom; c.beginPath();c.moveTo(tx,ty);c.lineTo(tx-s*Math.cos(a-Math.PI/6),ty-s*Math.sin(a-Math.PI/6));c.lineTo(tx-s*Math.cos(a+Math.PI/6),ty-s*Math.sin(a+Math.PI/6));c.closePath();c.fill();c.stroke(); }

// ===== STATE MACHINE =====
function drawStateMachine(c,d,tc){
    (d.transitions||[]).forEach(t=>drawTransition(c,t,d,tc));
    (d.states||[]).forEach(s=>drawState(c,s,tc));
}

function drawState(c,s,tc){
    const isS = state.selectedElement?.id===s.id;
    const selColor = tc.selected;
    const z = state.zoom;
    if(s.stateType==='initial'){ c.fillStyle = isS ? selColor : tc.stateInit; c.beginPath(); c.arc(s.x, s.y, 10/z, 0, Math.PI*2); c.fill(); if(isS){ c.strokeStyle=selColor; c.lineWidth=2/z; c.setLineDash([4/z,4/z]); c.beginPath(); c.arc(s.x,s.y,15/z,0,Math.PI*2); c.stroke(); c.setLineDash([]); } }
    else if(s.stateType==='final'){ c.strokeStyle = isS ? selColor : tc.stateFinalOuter; c.lineWidth = (isS ? 3 : 2)/z; c.beginPath(); c.arc(s.x, s.y, 14/z, 0, Math.PI*2); c.stroke(); c.fillStyle = isS ? selColor : tc.stateFinalInner; c.beginPath(); c.arc(s.x, s.y, 8/z, 0, Math.PI*2); c.fill(); }
    else if(s.stateType==='state'){ const w=s.width||120, h=s.height||50, r=12; c.fillStyle = isS ? (state.darkMode?'#2a2050':'#eef2ff') : tc.stateFill; c.strokeStyle = isS ? selColor : tc.stateStroke; c.lineWidth = (isS ? 3 : 2)/z; roundRect(c, s.x-w/2, s.y-h/2, w, h, r); c.fill(); c.stroke(); c.fillStyle = tc.textPrimary; c.font = `bold ${13/z}px Vazirmatn`; c.textAlign = 'center'; c.textBaseline = 'middle'; c.fillText(s.name, s.x, s.y); }
    else if(s.stateType==='composite'){ const w=s.width||180, h=s.height||120, r=12; c.fillStyle = isS ? (state.darkMode?'#1e2540':'#f0f4ff') : tc.stateFill; c.strokeStyle = isS ? selColor : tc.stateStroke; c.lineWidth = (isS ? 3 : 2)/z; roundRect(c, s.x-w/2, s.y-h/2, w, h, r); c.fill(); c.stroke(); const headerH = 28; c.beginPath(); c.moveTo(s.x-w/2, s.y-h/2+headerH); c.lineTo(s.x+w/2, s.y-h/2+headerH); c.stroke(); c.fillStyle = tc.textPrimary; c.font = `bold ${13/z}px Vazirmatn`; c.textAlign = 'center'; c.textBaseline = 'middle'; c.fillText(s.name, s.x, s.y-h/2+headerH/2); c.fillStyle = tc.textMuted; c.font = `${11/z}px Vazirmatn`; c.fillText('(زیرحالت‌ها)', s.x, s.y+10/z); }
}

function drawTransition(c,t,d,tc){
    const from = (d.states||[]).find(s=>s.id===t.from); const to = (d.states||[]).find(s=>s.id===t.to);
    if(!from||!to) return;
    const fp = {x:from.x, y:from.y}; const tp = {x:to.x, y:to.y};
    const z = state.zoom;
    if(t.from === t.to){ const loopR = 30/z; c.strokeStyle = tc.transStroke; c.lineWidth = 2/z; c.setLineDash([]); c.beginPath(); c.arc(fp.x + loopR, fp.y - loopR, loopR, Math.PI*0.3, Math.PI*1.7); c.stroke(); const endAngle = Math.PI*1.7; const ax = fp.x + loopR + loopR*Math.cos(endAngle); const ay = fp.y - loopR + loopR*Math.sin(endAngle); drawSmallArrow(c, ax, ay, endAngle + Math.PI/2, tc); if(t.label){ c.fillStyle = tc.transLabel; c.font = `${11/z}px Vazirmatn`; c.textAlign = 'center'; c.textBaseline = 'bottom'; c.fillText(t.label, fp.x + loopR*2 + 5/z, fp.y - loopR); } }
    else { c.strokeStyle = tc.transStroke; c.lineWidth = 2/z; c.setLineDash([]); c.beginPath(); c.moveTo(fp.x, fp.y); c.lineTo(tp.x, tp.y); c.stroke(); const angle = Math.atan2(tp.y-fp.y, tp.x-fp.x); drawSmallArrow(c, tp.x, tp.y, angle, tc); if(t.label){ const mx=(fp.x+tp.x)/2, my=(fp.y+tp.y)/2; const tw = c.measureText(t.label).width + 10/z; c.fillStyle = tc.labelBg; c.fillRect(mx-tw/2, my-18/z, tw, 16/z); c.fillStyle = tc.transLabel; c.font = `${11/z}px Vazirmatn`; c.textAlign = 'center'; c.textBaseline = 'middle'; c.fillText(t.label, mx, my-10/z); } }
}

function drawSmallArrow(c, x, y, angle, tc){ const z=state.zoom; const len=10/z, a=Math.PI/6; c.fillStyle = tc.transStroke; c.beginPath(); c.moveTo(x, y); c.lineTo(x-len*Math.cos(angle-a), y-len*Math.sin(angle-a)); c.lineTo(x-len*Math.cos(angle+a), y-len*Math.sin(angle+a)); c.closePath(); c.fill(); }

// ===== ACTIVITY =====
function drawActivity(c,d,tc){ (d.flows||[]).forEach(f=>drawFlow(c,f,d,tc)); (d.nodes||[]).forEach(n=>drawNode(c,n,tc)); }

function drawNode(c,n,tc){
    const isS = state.selectedElement?.id===n.id; const selColor = tc.selected;
    const z = state.zoom;
    if(n.nodeType==='initialNode'){ c.fillStyle = isS ? selColor : tc.stateInit; c.beginPath(); c.arc(n.x, n.y, 10/z, 0, Math.PI*2); c.fill(); if(isS){ c.strokeStyle=selColor; c.lineWidth=2/z; c.setLineDash([4/z,4/z]); c.beginPath(); c.arc(n.x,n.y,15/z,0,Math.PI*2); c.stroke(); c.setLineDash([]); } }
    else if(n.nodeType==='finalNode'){ c.strokeStyle = isS ? selColor : tc.stateFinalOuter; c.lineWidth = (isS ? 3 : 2)/z; c.beginPath(); c.arc(n.x, n.y, 14/z, 0, Math.PI*2); c.stroke(); c.fillStyle = isS ? selColor : tc.stateFinalInner; c.beginPath(); c.arc(n.x, n.y, 8/z, 0, Math.PI*2); c.fill(); }
    else if(n.nodeType==='action'){ const w=n.width||120, h=n.height||50, r=12; c.fillStyle = isS ? (state.darkMode?'#1e3a2e':'#e6fffa') : tc.activityFill; c.strokeStyle = isS ? selColor : tc.activityStroke; c.lineWidth = (isS ? 3 : 2)/z; roundRect(c, n.x-w/2, n.y-h/2, w, h, r); c.fill(); c.stroke(); c.fillStyle = tc.textPrimary; c.font = `${13/z}px Vazirmatn`; c.textAlign = 'center'; c.textBaseline = 'middle'; c.fillText(n.name, n.x, n.y); }
    else if(n.nodeType==='decision' || n.nodeType==='merge'){ const size = n.size || 40; c.fillStyle = isS ? (state.darkMode?'#3a2e1e':'#fffaf0') : tc.decisionFill; c.strokeStyle = isS ? selColor : tc.decisionStroke; c.lineWidth = (isS ? 3 : 2)/z; c.beginPath(); c.moveTo(n.x, n.y-size/z); c.lineTo(n.x+size/z, n.y); c.lineTo(n.x, n.y+size/z); c.lineTo(n.x-size/z, n.y); c.closePath(); c.fill(); c.stroke(); if(n.name){ c.fillStyle = tc.textPrimary; c.font = `${11/z}px Vazirmatn`; c.textAlign = 'center'; c.textBaseline = 'middle'; c.fillText(n.name, n.x, n.y); } }
    else if(n.nodeType==='forkJoin'){ const w = n.orientation==='horizontal' ? (n.width||150) : 6/z; const h = n.orientation==='horizontal' ? 6/z : (n.height||150); c.fillStyle = isS ? selColor : tc.forkStroke; c.fillRect(n.x-w/2, n.y-h/2, w, h); if(isS){ c.strokeStyle = selColor; c.lineWidth = 2/z; c.setLineDash([4/z,4/z]); c.strokeRect(n.x-w/2-4/z, n.y-h/2-4/z, w+8/z, h+8/z); c.setLineDash([]); } }
}

function drawFlow(c,f,d,tc){ const from = (d.nodes||[]).find(n=>n.id===f.from); const to = (d.nodes||[]).find(n=>n.id===f.to); if(!from||!to) return; const fp = {x:from.x, y:from.y}; const tp = {x:to.x, y:to.y}; const z=state.zoom; c.strokeStyle = tc.flowStroke; c.lineWidth = 2/z; c.setLineDash([]); c.beginPath(); c.moveTo(fp.x, fp.y); c.lineTo(tp.x, tp.y); c.stroke(); const angle = Math.atan2(tp.y-fp.y, tp.x-fp.x); drawSmallArrow(c, tp.x, tp.y, angle, tc); if(f.label){ const mx=(fp.x+tp.x)/2, my=(fp.y+tp.y)/2; const tw = c.measureText(f.label).width + 10/z; c.fillStyle = tc.labelBg; c.fillRect(mx-tw/2, my-18/z, tw, 16/z); c.fillStyle = tc.flowLabel; c.font = `${11/z}px Vazirmatn`; c.textAlign = 'center'; c.textBaseline = 'middle'; c.fillText(f.label, mx, my-10/z); } }

// ===== COMPONENT =====
function drawComponent(c,d,tc){ (d.packages||[]).forEach(p=>drawPackage(c,p,tc)); (d.interfaces||[]).forEach(i=>drawInterface(c,i,tc)); (d.compRelations||[]).forEach(r=>drawCompRelation(c,r,d,tc)); (d.components||[]).forEach(comp=>drawComponentEl(c,comp,tc)); }

function drawComponentEl(c,comp,tc){ const isS = state.selectedElement?.id===comp.id; const z=state.zoom; const w=comp.width||140, h=comp.height||70; const x=comp.x-w/2, y=comp.y-h/2; c.fillStyle = isS ? (state.darkMode?'#3a2e55':'#f3e8ff') : tc.compFill; c.strokeStyle = isS ? tc.selected : tc.compStroke; c.lineWidth = (isS ? 3 : 2)/z; c.setLineDash([]); c.fillRect(x, y, w, h); c.strokeRect(x, y, w, h); const iconX = x + w - 25/z, iconY = y + 8/z; drawComponentIcon(c, iconX, iconY, tc); c.fillStyle = tc.textPrimary; c.font = `bold ${13/z}px Vazirmatn`; c.textAlign = 'center'; c.textBaseline = 'middle'; c.fillText(comp.name, comp.x, comp.y + 5/z); }

function drawComponentIcon(c, x, y, tc){ const z=state.zoom; c.strokeStyle = tc.compIcon; c.lineWidth = 1.5/z; c.strokeRect(x, y, 18/z, 14/z); c.fillStyle = tc.compIcon; c.fillRect(x-4/z, y+2/z, 4/z, 3/z); c.fillRect(x-4/z, y+9/z, 4/z, 3/z); }

function drawInterface(c,iface,tc){ const isS = state.selectedElement?.id===iface.id; const z=state.zoom; if(iface.ifaceType==='provided'){ const stickLen = 30/z; const dir = iface.direction || 'right'; let ex, ey; if(dir==='right'){ ex=iface.x+stickLen; ey=iface.y; } else if(dir==='left'){ ex=iface.x-stickLen; ey=iface.y; } else if(dir==='up'){ ex=iface.x; ey=iface.y-stickLen; } else { ex=iface.x; ey=iface.y+stickLen; } c.strokeStyle = isS ? tc.selected : tc.ifaceStroke; c.lineWidth = (isS ? 3 : 2)/z; c.setLineDash([]); c.beginPath(); c.moveTo(iface.x, iface.y); c.lineTo(ex, ey); c.stroke(); c.beginPath(); c.arc(ex, ey, 8/z, 0, Math.PI*2); c.stroke(); if(iface.name){ c.fillStyle = tc.textPrimary; c.font = `${11/z}px Vazirmatn`; c.textAlign = 'center'; c.textBaseline = 'bottom'; c.fillText(iface.name, ex, ey-12/z); } } else { const dir = iface.direction || 'right'; c.strokeStyle = isS ? tc.selected : tc.ifaceStroke; c.lineWidth = (isS ? 3 : 2)/z; c.setLineDash([]); let startAngle, endAngle; if(dir==='right'){ startAngle=-Math.PI/2; endAngle=Math.PI/2; } else if(dir==='left'){ startAngle=Math.PI/2; endAngle=-Math.PI/2; } else if(dir==='up'){ startAngle=Math.PI; endAngle=0; } else { startAngle=0; endAngle=Math.PI; } c.beginPath(); c.arc(iface.x, iface.y, 10/z, startAngle, endAngle); c.stroke(); if(iface.name){ c.fillStyle = tc.textPrimary; c.font = `${11/z}px Vazirmatn`; c.textAlign = 'center'; c.textBaseline = 'bottom'; c.fillText(iface.name, iface.x, iface.y-14/z); } } }

function drawPackage(c,pkg,tc){ const isS = state.selectedElement?.id===pkg.id; const z=state.zoom; const w=pkg.width||200, h=pkg.height||150; const x=pkg.x, y=pkg.y; const tabW = 60/z, tabH = 18/z; c.fillStyle = isS ? (state.darkMode?'#3a2e1e':'#fffaf0') : tc.pkgFill; c.strokeStyle = isS ? tc.selected : tc.pkgStroke; c.lineWidth = (isS ? 3 : 2)/z; c.setLineDash([]); c.beginPath(); c.moveTo(x, y); c.lineTo(x+tabW, y); c.lineTo(x+tabW, y-tabH); c.lineTo(x, y-tabH); c.closePath(); c.fill(); c.stroke(); c.fillRect(x, y, w, h); c.strokeRect(x, y, w, h); c.fillStyle = tc.textPrimary; c.font = `bold ${13/z}px Vazirmatn`; c.textAlign = 'center'; c.textBaseline = 'middle'; c.fillText(pkg.name, x+w/2, y-tabH/2); }

function drawCompRelation(c,r,d,tc){ const from = findElInComp(r.from,d); const to = findElInComp(r.to,d); if(!from||!to) return; const fp = elCenter(from), tp = elCenter(to); const z=state.zoom; if(r.relType==='dependency'){ c.strokeStyle = tc.depStroke; c.lineWidth = 2/z; c.setLineDash([6/z,4/z]); c.beginPath(); c.moveTo(fp.x,fp.y); c.lineTo(tp.x,tp.y); c.stroke(); c.setLineDash([]); const angle = Math.atan2(tp.y-fp.y, tp.x-fp.x); drawOpenArrow(c, tp.x, tp.y, angle, tc); if(r.label){ const mx=(fp.x+tp.x)/2, my=(fp.y+tp.y)/2; c.fillStyle = tc.labelBg; const tw = c.measureText(r.label).width + 8/z; c.fillRect(mx-tw/2, my-18/z, tw, 16/z); c.fillStyle = tc.textMuted; c.font = `${11/z}px Vazirmatn`; c.textAlign = 'center'; c.textBaseline = 'middle'; c.fillText(r.label, mx, my-10/z); } } else if(r.relType==='connector'){ c.strokeStyle = tc.depStroke; c.lineWidth = 2/z; c.setLineDash([]); c.beginPath(); c.moveTo(fp.x,fp.y); c.lineTo(tp.x,tp.y); c.stroke(); } else if(r.relType==='compAssociation'){ c.strokeStyle = tc.depStroke; c.lineWidth = 2/z; c.setLineDash([]); c.beginPath(); c.moveTo(fp.x,fp.y); c.lineTo(tp.x,tp.y); c.stroke(); if(r.label){ const mx=(fp.x+tp.x)/2, my=(fp.y+tp.y)/2; c.fillStyle = tc.labelBg; const tw = c.measureText(r.label).width + 8/z; c.fillRect(mx-tw/2, my-18/z, tw, 16/z); c.fillStyle = tc.textMuted; c.font = `${11/z}px Vazirmatn`; c.textAlign = 'center'; c.textBaseline = 'middle'; c.fillText(r.label, mx, my-10/z); } } }

function drawOpenArrow(c, x, y, angle, tc){ const z=state.zoom; const len=12/z, a=Math.PI/6; c.strokeStyle = tc.depStroke; c.lineWidth = 2/z; c.beginPath(); c.moveTo(x-len*Math.cos(angle-a), y-len*Math.sin(angle-a)); c.lineTo(x, y); c.lineTo(x-len*Math.cos(angle+a), y-len*Math.sin(angle+a)); c.stroke(); }

// ===== DEPLOYMENT =====
function drawDeployment(c,d,tc){ (d.deployRelations||[]).forEach(r=>drawDeployRelation(c,r,d,tc)); (d.artifacts||[]).forEach(a=>drawArtifact(c,a,tc)); (d.deployNodes||[]).forEach(n=>drawDeployNode(c,n,tc)); }

function drawDeployNode(c,node,tc){ const isS = state.selectedElement?.id===node.id; const z=state.zoom; const w=node.width||160, h=node.height||100; const offset = 20/z; const x=node.x-w/2, y=node.y-h/2; let topFace = tc.nodeFaceTop, frontFace = tc.nodeFaceFront, sideFace = tc.nodeFaceSide; if(node.nodeType==='device'){ topFace = state.darkMode?'#1e3a3a':'#b2f5ea'; frontFace = state.darkMode?'#1a3030':'#81e6d9'; sideFace = state.darkMode?'#162828':'#4fd1c5'; } else if(node.nodeType==='execEnv'){ topFace = state.darkMode?'#3a2e1e':'#fefcbf'; frontFace = state.darkMode?'#302818':'#f6e05e'; sideFace = state.darkMode?'#282010':'#ecc94b'; } if(isS){ topFace = '#4c51bf'; frontFace = '#5a67d8'; sideFace = '#667eea'; } c.fillStyle = frontFace; c.strokeStyle = tc.nodeStroke; c.lineWidth = (isS ? 3 : 2)/z; c.beginPath(); c.moveTo(x, y); c.lineTo(x+w, y); c.lineTo(x+w, y+h); c.lineTo(x, y+h); c.closePath(); c.fill(); c.stroke(); c.fillStyle = topFace; c.beginPath(); c.moveTo(x, y); c.lineTo(x+offset, y-offset); c.lineTo(x+w+offset, y-offset); c.lineTo(x+w, y); c.closePath(); c.fill(); c.stroke(); c.fillStyle = sideFace; c.beginPath(); c.moveTo(x+w, y); c.lineTo(x+w+offset, y-offset); c.lineTo(x+w+offset, y+h-offset); c.lineTo(x+w, y+h); c.closePath(); c.fill(); c.stroke(); const stereotype = node.nodeType==='device'?'«device»':node.nodeType==='execEnv'?'«executionEnvironment»':'«node»'; c.fillStyle = tc.textMuted; c.font = `italic ${10/z}px Vazirmatn`; c.textAlign = 'center'; c.textBaseline = 'bottom'; c.fillText(stereotype, node.x, y-2/z); c.fillStyle = tc.textPrimary; c.font = `bold ${13/z}px Vazirmatn`; c.textAlign = 'center'; c.textBaseline = 'middle'; c.fillText(node.name, node.x, node.y); }

function drawArtifact(c,art,tc){ const isS = state.selectedElement?.id===art.id; const z=state.zoom; const w=art.width||120, h=art.height||50; const x=art.x-w/2, y=art.y-h/2; const corner = 12/z; c.fillStyle = isS ? (state.darkMode?'#3b1a1a':'#fff5f5') : tc.artifactFill; c.strokeStyle = isS ? tc.selected : tc.artifactStroke; c.lineWidth = (isS ? 3 : 2)/z; c.setLineDash([]); c.beginPath(); c.moveTo(x, y); c.lineTo(x+w-corner, y); c.lineTo(x+w, y+corner); c.lineTo(x+w, y+h); c.lineTo(x, y+h); c.closePath(); c.fill(); c.stroke(); c.beginPath(); c.moveTo(x+w-corner, y); c.lineTo(x+w-corner, y+corner); c.lineTo(x+w, y+corner); c.stroke(); c.fillStyle = tc.textMuted; c.font = `italic ${10/z}px Vazirmatn`; c.textAlign = 'center'; c.textBaseline = 'bottom'; c.fillText('«artifact»', art.x, y-2/z); c.fillStyle = tc.textPrimary; c.font = `${13/z}px Vazirmatn`; c.textAlign = 'center'; c.textBaseline = 'middle'; c.fillText(art.name, art.x, art.y+5/z); }

function drawDeployRelation(c,r,d,tc){ const from = findElInDeploy(r.from,d); const to = findElInDeploy(r.to,d); if(!from||!to) return; const fp = elCenter(from), tp = elCenter(to); const z=state.zoom; if(r.relType==='commPath'){ c.strokeStyle = tc.depStroke; c.lineWidth = 2/z; c.setLineDash([]); c.beginPath(); c.moveTo(fp.x,fp.y); c.lineTo(tp.x,tp.y); c.stroke(); if(r.label){ const mx=(fp.x+tp.x)/2, my=(fp.y+tp.y)/2; c.fillStyle = tc.labelBg; const tw = c.measureText(r.label).width + 8/z; c.fillRect(mx-tw/2, my-18/z, tw, 16/z); c.fillStyle = tc.textMuted; c.font = `${11/z}px Vazirmatn`; c.textAlign = 'center'; c.textBaseline = 'middle'; c.fillText(r.label, mx, my-10/z); } } else if(r.relType==='deployRel'){ c.strokeStyle = tc.depStroke; c.lineWidth = 2/z; c.setLineDash([6/z,4/z]); c.beginPath(); c.moveTo(fp.x,fp.y); c.lineTo(tp.x,tp.y); c.stroke(); c.setLineDash([]); const angle = Math.atan2(tp.y-fp.y, tp.x-fp.x); drawOpenArrow(c, tp.x, tp.y, angle, tc); const mx=(fp.x+tp.x)/2, my=(fp.y+tp.y)/2; const label = r.label || '«deploy»'; const tw = c.measureText(label).width + 8/z; c.fillStyle = tc.labelBg; c.fillRect(mx-tw/2, my-18/z, tw, 16/z); c.fillStyle = tc.textMuted; c.font = `italic ${11/z}px Vazirmatn`; c.textAlign = 'center'; c.textBaseline = 'middle'; c.fillText(label, mx, my-10/z); } else if(r.relType==='manifestRel'){ c.strokeStyle = tc.depStroke; c.lineWidth = 2/z; c.setLineDash([6/z,4/z]); c.beginPath(); c.moveTo(fp.x,fp.y); c.lineTo(tp.x,tp.y); c.stroke(); c.setLineDash([]); drawHollowTriangle(c, tp.x, tp.y, fp.x, fp.y, tc); if(r.label){ const mx=(fp.x+tp.x)/2, my=(fp.y+tp.y)/2; c.fillStyle = tc.labelBg; const tw = c.measureText(r.label).width + 8/z; c.fillRect(mx-tw/2, my-18/z, tw, 16/z); c.fillStyle = tc.textMuted; c.font = `${11/z}px Vazirmatn`; c.textAlign = 'center'; c.textBaseline = 'middle'; c.fillText(r.label, mx, my-10/z); } } }

function drawHollowTriangle(c, tx, ty, fx, fy, tc){ const z=state.zoom; const angle = Math.atan2(ty-fy, tx-fx); const s = 12/z; c.fillStyle = tc.labelBg; c.strokeStyle = tc.depStroke; c.lineWidth = 2/z; c.beginPath(); c.moveTo(tx, ty); c.lineTo(tx-s*Math.cos(angle-Math.PI/6), ty-s*Math.sin(angle-Math.PI/6)); c.lineTo(tx-s*Math.cos(angle+Math.PI/6), ty-s*Math.sin(angle+Math.PI/6)); c.closePath(); c.fill(); c.stroke(); }

// ===== CLASS DIAGRAM =====
function drawClassDiagram(c,d,tc){
    (d.classRelations||[]).forEach(r=>drawClassRelation(c,r,d,tc));
    (d.classes||[]).forEach(cls=>drawClassEl(c,cls,tc));
}

function drawClassEl(c,cls,tc){
    const isS = state.selectedElement?.id===cls.id;
    const z=state.zoom;
    const w=cls.width||180;
    const headerH=30/z;
    const attrH=Math.max(20/z, (cls.attributes||[]).length*18/z + 10/z);
    const methH=Math.max(20/z, (cls.methods||[]).length*18/z + 10/z);
    const h=headerH+attrH+methH;
    cls._computedH = h;
    
    const x=cls.x-w/2, y=cls.y-h/2;
    
    c.fillStyle = isS ? (state.darkMode?'#1e3a55':'#dbeafe') : tc.classFill;
    c.strokeStyle = isS ? tc.selected : tc.classStroke;
    c.lineWidth = (isS ? 3 : 2)/z;
    c.setLineDash([]);
    
    c.fillRect(x, y, w, h);
    c.strokeRect(x, y, w, h);
    
    c.fillStyle = tc.classHeader;
    c.fillRect(x, y, w, headerH);
    c.strokeRect(x, y, w, headerH);
    
    let stereotype = '';
    if(cls.classType==='interface') stereotype = '«interface»';
    else if(cls.classType==='abstract') stereotype = '«abstract»';
    else if(cls.classType==='enumeration') stereotype = '«enumeration»';
    
    if(stereotype){
        c.fillStyle = tc.textMuted;
        c.font = `italic ${10/z}px Vazirmatn`;
        c.textAlign = 'center'; c.textBaseline = 'bottom';
        c.fillText(stereotype, cls.x, y+13/z);
    }
    
    c.fillStyle = tc.textPrimary;
    c.font = cls.classType==='abstract' ? `italic bold ${13/z}px Vazirmatn` : `bold ${13/z}px Vazirmatn`;
    c.textAlign = 'center'; c.textBaseline = 'middle';
    const nameY = stereotype ? y+headerH/2+5/z : y+headerH/2;
    c.fillText(cls.name, cls.x, nameY);
    
    c.fillStyle = tc.classAttr;
    c.font = `${11/z}px Vazirmatn`;
    c.textAlign = 'left'; c.textBaseline = 'top';
    let attrY = y + headerH + 5/z;
    if((cls.attributes||[]).length === 0){
        c.fillStyle = tc.textMuted;
        c.fillText('(بدون ویژگی)', x+8/z, attrY);
    } else {
        (cls.attributes||[]).forEach(attr=>{
            const vis = attr.visibility || '+';
            const staticMark = attr.isStatic ? '_' : '';
            const typeStr = attr.type ? ': '+attr.type : '';
            const text = `${vis} ${staticMark}${attr.name}${typeStr}`;
            c.fillStyle = tc.classAttr;
            c.fillText(text, x+8/z, attrY);
            attrY += 18/z;
        });
    }
    
    const sep1Y = y + headerH + attrH;
    c.beginPath(); c.moveTo(x, sep1Y); c.lineTo(x+w, sep1Y); c.stroke();
    
    c.font = `${11/z}px Vazirmatn`;
    let methY = sep1Y + 5/z;
    if((cls.methods||[]).length === 0){
        c.fillStyle = tc.textMuted;
        c.fillText('(بدون متد)', x+8/z, methY);
    } else {
        (cls.methods||[]).forEach(meth=>{
            const vis = meth.visibility || '+';
            const staticMark = meth.isStatic ? '_' : '';
            const params = meth.params ? `(${meth.params})` : '()';
            const retType = meth.returnType ? ': '+meth.returnType : '';
            const text = `${vis} ${staticMark}${meth.name}${params}${retType}`;
            c.fillStyle = tc.classMethod;
            c.fillText(text, x+8/z, methY);
            methY += 18/z;
        });
    }
}

function drawClassRelation(c,r,d,tc){
    const from = (d.classes||[]).find(cls=>cls.id===r.from);
    const to = (d.classes||[]).find(cls=>cls.id===r.to);
    if(!from||!to) return;
    
    const fp = {x:from.x, y:from.y};
    const tp = {x:to.x, y:to.y};
    const angle = Math.atan2(tp.y-fp.y, tp.x-fp.x);
    const z=state.zoom;
    
    c.strokeStyle = tc.depStroke;
    c.lineWidth = 2/z;
    
    if(r.relType==='clsAssociation'){
        c.setLineDash([]);
        c.beginPath(); c.moveTo(fp.x,fp.y); c.lineTo(tp.x,tp.y); c.stroke();
    } else if(r.relType==='aggregation'){
        c.setLineDash([]);
        c.beginPath(); c.moveTo(fp.x,fp.y); c.lineTo(tp.x,tp.y); c.stroke();
        drawDiamond(c, fp.x, fp.y, angle, false, tc);
    } else if(r.relType==='composition'){
        c.setLineDash([]);
        c.beginPath(); c.moveTo(fp.x,fp.y); c.lineTo(tp.x,tp.y); c.stroke();
        drawDiamond(c, fp.x, fp.y, angle, true, tc);
    } else if(r.relType==='inheritance'){
        c.setLineDash([]);
        c.beginPath(); c.moveTo(fp.x,fp.y); c.lineTo(tp.x,tp.y); c.stroke();
        drawHollowTriangle(c, tp.x, tp.y, fp.x, fp.y, tc);
    } else if(r.relType==='clsDependency'){
        c.setLineDash([6/z,4/z]);
        c.beginPath(); c.moveTo(fp.x,fp.y); c.lineTo(tp.x,tp.y); c.stroke();
        c.setLineDash([]);
        drawOpenArrow(c, tp.x, tp.y, angle, tc);
    } else if(r.relType==='realization'){
        c.setLineDash([6/z,4/z]);
        c.beginPath(); c.moveTo(fp.x,fp.y); c.lineTo(tp.x,tp.y); c.stroke();
        c.setLineDash([]);
        drawHollowTriangle(c, tp.x, tp.y, fp.x, fp.y, tc);
    }
    
    if(r.label || r.fromMultiplicity || r.toMultiplicity){
        const mx=(fp.x+tp.x)/2, my=(fp.y+tp.y)/2;
        if(r.label){
            const tw = c.measureText(r.label).width + 8/z;
            c.fillStyle = tc.labelBg; c.fillRect(mx-tw/2, my-18/z, tw, 16/z);
            c.fillStyle = tc.textMuted; c.font = `${11/z}px Vazirmatn`;
            c.textAlign = 'center'; c.textBaseline = 'middle';
            c.fillText(r.label, mx, my-10/z);
        }
        if(r.fromMultiplicity){
            c.fillStyle = tc.textMuted; c.font = `${10/z}px Vazirmatn`;
            c.textAlign = 'left'; c.textBaseline = 'bottom';
            c.fillText(r.fromMultiplicity, fp.x+5/z, fp.y-3/z);
        }
        if(r.toMultiplicity){
            c.fillStyle = tc.textMuted; c.font = `${10/z}px Vazirmatn`;
            c.textAlign = 'right'; c.textBaseline = 'bottom';
            c.fillText(r.toMultiplicity, tp.x-5/z, tp.y-3/z);
        }
    }
}

function drawDiamond(c, x, y, angle, filled, tc){
    const z=state.zoom;
    const size = 10/z;
    c.save();
    c.translate(x, y);
    c.rotate(angle);
    c.beginPath();
    c.moveTo(0, 0);
    c.lineTo(-size, -size/2);
    c.lineTo(-size*2, 0);
    c.lineTo(-size, size/2);
    c.closePath();
    if(filled){
        c.fillStyle = tc.depStroke;
        c.fill();
    } else {
        c.fillStyle = tc.labelBg;
        c.fill();
    }
    c.strokeStyle = tc.depStroke;
    c.lineWidth = 2/z;
    c.stroke();
    c.restore();
}

// ===== SEQUENCE DIAGRAM =====
function drawSequenceDiagram(c,d,tc){
    (d.fragments||[]).forEach(f=>drawFragment(c,f,tc));
    (d.participants||[]).forEach(p=>drawParticipant(c,p,d,tc));
    (d.activations||[]).forEach(a=>drawActivation(c,a,d,tc));
    (d.messages||[]).forEach(m=>drawMessage(c,m,d,tc));
}

function drawParticipant(c,p,d,tc){
    const isS = state.selectedElement?.id===p.id;
    const z=state.zoom;
    const w=p.width||120, h=40/z;
    const x=p.x-w/2, y=p.y-h/2;
    
    c.fillStyle = isS ? (state.darkMode?'#2a3a55':'#cbd5e0') : tc.seqParticipantFill;
    c.strokeStyle = isS ? tc.selected : tc.seqParticipantStroke;
    c.lineWidth = (isS ? 3 : 2)/z;
    c.setLineDash([]);
    c.fillRect(x, y, w, h);
    c.strokeRect(x, y, w, h);
    
    c.fillStyle = tc.textPrimary;
    c.font = `bold ${12/z}px Vazirmatn`;
    c.textAlign = 'center'; c.textBaseline = 'middle';
    c.fillText(p.name, p.x, p.y);
    
    const canvasBottom = (canvas.height - state.panY) / state.zoom - 20/z;
    c.strokeStyle = tc.seqLifeline;
    c.lineWidth = 1/z;
    c.setLineDash([5/z,5/z]);
    c.beginPath();
    c.moveTo(p.x, p.y+h/2);
    c.lineTo(p.x, canvasBottom);
    c.stroke();
    c.setLineDash([]);
}

function drawActivation(c,a,d,tc){
    const isS = state.selectedElement?.id===a.id;
    const z=state.zoom;
    const participant = (d.participants||[]).find(p=>p.id===a.participantId);
    if(!participant) return;
    
    const w=12/z, h=a.height||60/z;
    const x=participant.x-w/2, y=a.y;
    
    c.fillStyle = isS ? tc.selected : tc.seqActivation;
    c.strokeStyle = isS ? tc.selected : tc.seqActivationStroke;
    c.lineWidth = (isS ? 2 : 1.5)/z;
    c.setLineDash([]);
    c.fillRect(x, y, w, h);
    c.strokeRect(x, y, w, h);
}

function drawFragment(c,f,tc){
    const isS = state.selectedElement?.id===f.id;
    const z=state.zoom;
    const w=f.width||300/z, h=f.height||150/z;
    const x=f.x, y=f.y;
    const tabW=50/z, tabH=20/z;
    
    c.fillStyle = isS ? (state.darkMode?'#3a2e1e':'#fefcbf') : tc.seqFragmentFill;
    c.strokeStyle = isS ? tc.selected : tc.seqFragmentStroke;
    c.lineWidth = (isS ? 3 : 2)/z;
    c.setLineDash([]);
    
    c.fillRect(x, y, w, h);
    c.strokeRect(x, y, w, h);
    
    c.beginPath();
    c.moveTo(x, y);
    c.lineTo(x+tabW, y);
    c.lineTo(x+tabW, y+tabH);
    c.lineTo(x, y+tabH);
    c.closePath();
    c.fill(); c.stroke();
    
    const typeLabel = f.fragmentType==='alt'?'alt':f.fragmentType==='opt'?'opt':f.fragmentType==='loop'?'loop':'break';
    c.fillStyle = tc.textPrimary;
    c.font = `bold italic ${11/z}px Vazirmatn`;
    c.textAlign = 'center'; c.textBaseline = 'middle';
    c.fillText(`[${typeLabel}]`, x+tabW/2, y+tabH/2);
    
    if(f.label){
        c.fillStyle = tc.textMuted;
        c.font = `italic ${10/z}px Vazirmatn`;
        c.textAlign = 'left'; c.textBaseline = 'middle';
        c.fillText(f.label, x+tabW+5/z, y+tabH/2);
    }
    
    if(f.operands && f.operands.length > 1){
        c.strokeStyle = tc.seqFragmentStroke;
        c.lineWidth = 1/z;
        c.setLineDash([4/z,4/z]);
        let currentY = y + tabH;
        f.operands.forEach((op, idx)=>{
            if(idx < f.operands.length - 1){
                currentY += (op.height || 40/z);
                c.beginPath();
                c.moveTo(x, currentY);
                c.lineTo(x+w, currentY);
                c.stroke();
                if(op.label){
                    c.fillStyle = tc.textMuted;
                    c.font = `italic ${10/z}px Vazirmatn`;
                    c.textAlign = 'left'; c.textBaseline = 'middle';
                    c.fillText(`[${op.label}]`, x+5/z, currentY-8/z);
                }
            }
        });
        c.setLineDash([]);
    }
}

function drawMessage(c,m,d,tc){
    const fromP = (d.participants||[]).find(p=>p.id===m.fromParticipant);
    const toP = (d.participants||[]).find(p=>p.id===m.toParticipant);
    if(!fromP || !toP) return;
    
    const z=state.zoom;
    const y = m.y;
    const fromX = fromP.x;
    const toX = toP.x;
    
    c.lineWidth = 2/z;
    c.setLineDash([]);
    
    if(m.msgType==='syncMsg'){
        c.strokeStyle = tc.seqMsg;
        c.beginPath(); c.moveTo(fromX, y); c.lineTo(toX, y); c.stroke();
        const angle = toX > fromX ? 0 : Math.PI;
        drawFilledArrow(c, toX, y, angle, tc);
    } else if(m.msgType==='asyncMsg'){
        c.strokeStyle = tc.seqMsg;
        c.beginPath(); c.moveTo(fromX, y); c.lineTo(toX, y); c.stroke();
        const angle = toX > fromX ? 0 : Math.PI;
        drawOpenArrowSide(c, toX, y, angle, tc);
    } else if(m.msgType==='returnMsg'){
        c.strokeStyle = tc.seqReturn;
        c.setLineDash([5/z,5/z]);
        c.beginPath(); c.moveTo(fromX, y); c.lineTo(toX, y); c.stroke();
        c.setLineDash([]);
        const angle = toX > fromX ? 0 : Math.PI;
        drawOpenArrowSide(c, toX, y, angle, tc);
    } else if(m.msgType==='selfMsg'){
        c.strokeStyle = tc.seqMsg;
        const loopW = 30/z, loopH = 20/z;
        c.beginPath();
        c.moveTo(fromX, y);
        c.lineTo(fromX+loopW, y);
        c.lineTo(fromX+loopW, y+loopH);
        c.lineTo(fromX, y+loopH);
        c.stroke();
        drawFilledArrow(c, fromX, y+loopH, Math.PI, tc);
    }
    
    if(m.label){
        const mx = (fromX + toX) / 2;
        c.fillStyle = tc.labelBg;
        const tw = c.measureText(m.label).width + 8/z;
        c.fillRect(mx-tw/2, y-18/z, tw, 16/z);
        c.fillStyle = tc.textPrimary;
        c.font = `${11/z}px Vazirmatn`;
        c.textAlign = 'center'; c.textBaseline = 'middle';
        c.fillText(m.label, mx, y-10/z);
    }
}

function drawFilledArrow(c, x, y, angle, tc){
    const z=state.zoom;
    const len=10/z, a=Math.PI/6;
    c.fillStyle = tc.seqMsg;
    c.beginPath();
    c.moveTo(x, y);
    c.lineTo(x-len*Math.cos(angle-a), y-len*Math.sin(angle-a));
    c.lineTo(x-len*Math.cos(angle+a), y-len*Math.sin(angle+a));
    c.closePath();
    c.fill();
}

function drawOpenArrowSide(c, x, y, angle, tc){
    const z=state.zoom;
    const len=10/z, a=Math.PI/6;
    c.strokeStyle = tc.seqMsg;
    c.lineWidth = 2/z;
    c.beginPath();
    c.moveTo(x-len*Math.cos(angle-a), y-len*Math.sin(angle-a));
    c.lineTo(x, y);
    c.lineTo(x-len*Math.cos(angle+a), y-len*Math.sin(angle+a));
    c.stroke();
}

// ===== SWIMLANE DIAGRAM =====
function drawSwimlaneDiagram(c,d,tc){
    (d.swimlanes||[]).forEach(sw=>drawSwimlane(c,sw,d,tc));
    (d.swFlows||[]).forEach(f=>drawSwFlow(c,f,d,tc));
    (d.swNodes||[]).forEach(n=>drawSwNode(c,n,tc));
}

function drawSwimlane(c,sw,d,tc){
    const isS = state.selectedElement?.id===sw.id;
    const z=state.zoom;
    const w=sw.width||400/z, h=sw.height||300/z;
    const x=sw.x, y=sw.y;
    const headerSize = 40/z;
    
    const fillColor = sw.color || tc.swimlaneDefault;
    c.fillStyle = isS ? (state.darkMode?'#2a3a55':'#dbeafe') : fillColor;
    c.strokeStyle = isS ? tc.selected : tc.swimlaneStroke;
    c.lineWidth = (isS ? 3 : 2)/z;
    c.setLineDash([]);
    
    c.fillRect(x, y, w, h);
    c.strokeRect(x, y, w, h);
    
    if(sw.orientation==='vertical'){
        c.fillStyle = tc.swimlaneStroke;
        c.fillRect(x+w-headerSize, y, headerSize, h);
        c.strokeRect(x+w-headerSize, y, headerSize, h);
        
        c.save();
        c.translate(x+w-headerSize/2, y+h/2);
        c.rotate(-Math.PI/2);
        c.fillStyle = tc.textPrimary;
        c.font = `bold ${13/z}px Vazirmatn`;
        c.textAlign = 'center';
        c.textBaseline = 'middle';
        c.fillText(sw.name, 0, 0);
        c.restore();
    } else {
        c.fillStyle = tc.swimlaneStroke;
        c.fillRect(x, y, w, headerSize);
        c.strokeRect(x, y, w, headerSize);
        
        c.fillStyle = tc.textPrimary;
        c.font = `bold ${13/z}px Vazirmatn`;
        c.textAlign = 'center';
        c.textBaseline = 'middle';
        c.fillText(sw.name, x+w/2, y+headerSize/2);
    }
    
    if(isS){
        drawResizeHandles(c, sw);
    }
}

function drawResizeHandles(c, sw){
    const handles = getResizeHandles(sw);
    const z=state.zoom;
    c.fillStyle = '#667eea';
    c.strokeStyle = '#ffffff';
    c.lineWidth = 2/z;
    
    handles.forEach(h=>{
        c.fillRect(h.x-4/z, h.y-4/z, 8/z, 8/z);
        c.strokeRect(h.x-4/z, h.y-4/z, 8/z, 8/z);
    });
}

function getResizeHandles(sw){
    const z=state.zoom;
    const w=(sw.width||400/z), h=(sw.height||300/z);
    const x=sw.x, y=sw.y;
    return [
        {x:x, y:y, pos:'tl'},
        {x:x+w, y:y, pos:'tr'},
        {x:x, y:y+h, pos:'bl'},
        {x:x+w, y:y+h, pos:'br'},
        {x:x+w/2, y:y, pos:'t'},
        {x:x+w/2, y:y+h, pos:'b'},
        {x:x, y:y+h/2, pos:'l'},
        {x:x+w, y:y+h/2, pos:'r'}
    ];
}

function drawSwNode(c,n,tc){
    const isS = state.selectedElement?.id===n.id;
    const selColor = tc.selected;
    const z=state.zoom;

    if(n.nodeType==='swInitial'){
        c.fillStyle = isS ? selColor : tc.stateInit;
        c.beginPath(); c.arc(n.x, n.y, 10/z, 0, Math.PI*2); c.fill();
        if(isS){ c.strokeStyle=selColor; c.lineWidth=2/z; c.setLineDash([4/z,4/z]); c.beginPath(); c.arc(n.x,n.y,15/z,0,Math.PI*2); c.stroke(); c.setLineDash([]); }
    }
    else if(n.nodeType==='swFinal'){
        c.strokeStyle = isS ? selColor : tc.stateFinalOuter;
        c.lineWidth = (isS ? 3 : 2)/z;
        c.beginPath(); c.arc(n.x, n.y, 14/z, 0, Math.PI*2); c.stroke();
        c.fillStyle = isS ? selColor : tc.stateFinalInner;
        c.beginPath(); c.arc(n.x, n.y, 8/z, 0, Math.PI*2); c.fill();
    }
    else if(n.nodeType==='swAction'){
        const w=n.width||120/z, h=n.height||50/z, r=12/z;
        c.fillStyle = isS ? (state.darkMode?'#1e3a2e':'#e6fffa') : tc.activityFill;
        c.strokeStyle = isS ? selColor : tc.activityStroke;
        c.lineWidth = (isS ? 3 : 2)/z;
        roundRect(c, n.x-w/2, n.y-h/2, w, h, r);
        c.fill(); c.stroke();
        c.fillStyle = tc.textPrimary; c.font = `${13/z}px Vazirmatn`;
        c.textAlign = 'center'; c.textBaseline = 'middle';
        c.fillText(n.name, n.x, n.y);
    }
    else if(n.nodeType==='swDecision'){
        const size = (n.size || 40)/z;
        c.fillStyle = isS ? (state.darkMode?'#3a2e1e':'#fffaf0') : tc.decisionFill;
        c.strokeStyle = isS ? selColor : tc.decisionStroke;
        c.lineWidth = (isS ? 3 : 2)/z;
        c.beginPath();
        c.moveTo(n.x, n.y-size);
        c.lineTo(n.x+size, n.y);
        c.lineTo(n.x, n.y+size);
        c.lineTo(n.x-size, n.y);
        c.closePath();
        c.fill(); c.stroke();
        if(n.name){
            c.fillStyle = tc.textPrimary; c.font = `${11/z}px Vazirmatn`;
            c.textAlign = 'center'; c.textBaseline = 'middle';
            c.fillText(n.name, n.x, n.y);
        }
    }
    else if(n.nodeType==='swForkJoin'){
        const w = n.orientation==='horizontal' ? (n.width||150/z) : 6/z;
        const h = n.orientation==='horizontal' ? 6/z : (n.height||150/z);
        c.fillStyle = isS ? selColor : tc.forkStroke;
        c.fillRect(n.x-w/2, n.y-h/2, w, h);
        if(isS){
            c.strokeStyle = selColor; c.lineWidth = 2/z; c.setLineDash([4/z,4/z]);
            c.strokeRect(n.x-w/2-4/z, n.y-h/2-4/z, w+8/z, h+8/z);
            c.setLineDash([]);
        }
    }
}

function drawSwFlow(c,f,d,tc){
    const from = (d.swNodes||[]).find(n=>n.id===f.from);
    const to = (d.swNodes||[]).find(n=>n.id===f.to);
    if(!from||!to) return;

    const fp = {x:from.x, y:from.y};
    const tp = {x:to.x, y:to.y};
    const z=state.zoom;

    c.strokeStyle = tc.flowStroke; c.lineWidth = 2/z; c.setLineDash([]);
    c.beginPath(); c.moveTo(fp.x, fp.y); c.lineTo(tp.x, tp.y); c.stroke();
    
    const angle = Math.atan2(tp.y-fp.y, tp.x-fp.x);
    drawSmallArrow(c, tp.x, tp.y, angle, tc);

    if(f.label){
        const mx=(fp.x+tp.x)/2, my=(fp.y+tp.y)/2;
        const tw = c.measureText(f.label).width + 10/z;
        c.fillStyle = tc.labelBg; c.fillRect(mx-tw/2, my-18/z, tw, 16/z);
        c.fillStyle = tc.flowLabel; c.font = `${11/z}px Vazirmatn`;
        c.textAlign = 'center'; c.textBaseline = 'middle';
        c.fillText(f.label, mx, my-10/z);
    }
}

function roundRect(c, x, y, w, h, r){ c.beginPath(); c.moveTo(x+r, y); c.lineTo(x+w-r, y); c.quadraticCurveTo(x+w, y, x+w, y+r); c.lineTo(x+w, y+h-r); c.quadraticCurveTo(x+w, y+h, x+w-r, y+h); c.lineTo(x+r, y+h); c.quadraticCurveTo(x, y+h, x, y+h-r); c.lineTo(x, y+r); c.quadraticCurveTo(x, y, x+r, y); c.closePath(); }

function drawSel(c,el,tc){
    const z=state.zoom;
    c.strokeStyle=tc.selected; c.lineWidth=2/z; c.setLineDash([5/z,5/z]);
    if(el.elType==='actor') c.strokeRect(el.x-25/z,el.y-35/z,50/z,80/z);
    else if(el.elType==='useCase'){c.beginPath();c.ellipse(el.x,el.y,el.radius*1.5+6/z,el.radius+6/z,0,0,Math.PI*2);c.stroke();}
    else if(el.elType==='systemBoundary') c.strokeRect(el.x-5/z,el.y-5/z,el.width+10/z,el.height+10/z);
    else if(el.elType==='state'||el.elType==='composite'){ const w=(el.width||120)+10/z,h=(el.height||50)+10/z; c.strokeRect(el.x-w/2,el.y-h/2,w,h); }
    else if(el.elType==='initial'||el.elType==='initialNode'||el.elType==='swInitial') { c.beginPath();c.arc(el.x,el.y,16/z,0,Math.PI*2);c.stroke(); }
    else if(el.elType==='final'||el.elType==='finalNode'||el.elType==='swFinal') { c.beginPath();c.arc(el.x,el.y,20/z,0,Math.PI*2);c.stroke(); }
    else if(el.elType==='action'||el.elType==='swAction'){ const w=(el.width||120)+10/z,h=(el.height||50)+10/z; c.strokeRect(el.x-w/2,el.y-h/2,w,h); }
    else if(el.elType==='decision'||el.elType==='merge'||el.elType==='swDecision'){ const s=(el.size||40)+6/z; c.strokeRect(el.x-s,el.y-s,s*2,s*2); }
    else if(el.elType==='forkJoin'||el.elType==='swForkJoin'){ const w=el.orientation==='horizontal'?(el.width||150):6/z, h=el.orientation==='horizontal'?6/z:(el.height||150); c.strokeRect(el.x-w/2-4/z,el.y-h/2-4/z,w+8/z,h+8/z); }
    else if(el.elType==='component'){ const w=(el.width||140)+10/z,h=(el.height||70)+10/z; c.strokeRect(el.x-w/2,el.y-h/2,w,h); }
    else if(el.elType==='interfaceProv'||el.elType==='interfaceReq'){ c.beginPath(); c.arc(el.x,el.y,20/z,0,Math.PI*2); c.stroke(); }
    else if(el.elType==='package'){ const w=(el.width||200)+10/z,h=(el.height||150)+28/z; c.strokeRect(el.x-5/z,el.y-23/z,w,h); }
    else if(el.elType==='node'||el.elType==='device'||el.elType==='execEnv'){ const w=(el.width||160)+30/z,h=(el.height||100)+30/z; c.strokeRect(el.x-w/2,el.y-h/2,w,h); }
    else if(el.elType==='artifact'){ const w=(el.width||120)+10/z,h=(el.height||50)+10/z; c.strokeRect(el.x-w/2,el.y-h/2,w,h); }
    else if(el.elType==='class'||el.elType==='interfaceClass'||el.elType==='abstractClass'||el.elType==='enumeration'){ const w=(el.width||180)+10/z,h=(el._computedH||100)+10/z; c.strokeRect(el.x-w/2,el.y-h/2,w,h); }
    else if(el.elType==='participant'){ const w=(el.width||120)+10/z,h=50/z; c.strokeRect(el.x-w/2,el.y-h/2,w,h); }
    else if(el.elType==='activation'){ c.strokeRect(el.x-10/z,el.y-4/z,20/z,(el.height||60/z)+8/z); }
    else if(el.elType==='fragment'){ const w=(el.width||300)+10/z,h=(el.height||150)+10/z; c.strokeRect(el.x-5/z,el.y-5/z,w,h); }
    else if(el.elType==='swimlane'){ const w=(el.width||400)+10/z,h=(el.height||300)+10/z; c.strokeRect(el.x-5/z,el.y-5/z,w,h); }
    c.setLineDash([]);
}

// ===== HELPERS =====
function findEl(id,d){
    if(d.diagramType==='usecase'){ let e=d.actors.find(a=>a.id===id); if(e) return {...e,elType:'actor'}; e=d.useCases.find(u=>u.id===id); if(e) return {...e,elType:'useCase'}; if(d.systemBoundary?.id===id) return {...d.systemBoundary,elType:'systemBoundary'}; }
    else if(d.diagramType==='statemachine'){ let e=(d.states||[]).find(s=>s.id===id); if(e) return {...e,elType:e.stateType}; }
    else if(d.diagramType==='activity'){ let e=(d.nodes||[]).find(n=>n.id===id); if(e) return {...e,elType:e.nodeType}; }
    else if(d.diagramType==='component'){ return findElInComp(id,d); }
    else if(d.diagramType==='deployment'){ return findElInDeploy(id,d); }
    else if(d.diagramType==='class'){ let e=(d.classes||[]).find(cls=>cls.id===id); if(e) return {...e,elType:e.classType}; }
    else if(d.diagramType==='sequence'){ let e=(d.participants||[]).find(p=>p.id===id); if(e) return {...e,elType:'participant'}; e=(d.activations||[]).find(a=>a.id===id); if(e) return {...e,elType:'activation'}; e=(d.fragments||[]).find(f=>f.id===id); if(e) return {...e,elType:'fragment'}; }
    else if(d.diagramType==='swimlane'){ let e=(d.swimlanes||[]).find(s=>s.id===id); if(e) return {...e,elType:'swimlane'}; e=(d.swNodes||[]).find(n=>n.id===id); if(e) return {...e,elType:e.nodeType}; }
    return null;
}

function findElInComp(id,d){ let e=(d.components||[]).find(c=>c.id===id); if(e) return {...e,elType:'component'}; e=(d.interfaces||[]).find(i=>i.id===id); if(e) return {...e,elType:e.ifaceType==='provided'?'interfaceProv':'interfaceReq'}; e=(d.packages||[]).find(p=>p.id===id); if(e) return {...e,elType:'package'}; return null; }
function findElInDeploy(id,d){ let e=(d.deployNodes||[]).find(n=>n.id===id); if(e) return {...e,elType:e.nodeType}; e=(d.artifacts||[]).find(a=>a.id===id); if(e) return {...e,elType:'artifact'}; return null; }

function elCenter(el){ return {x:el.x,y:el.y}; }

// Convert screen coordinates to world coordinates (accounting for zoom & pan)
function screenToWorld(sx, sy){
    return {
        x: (sx - state.panX) / state.zoom,
        y: (sy - state.panY) / state.zoom
    };
}

function hitTest(x,y){
    const d=getActive(); if(!d) return null;
    if(d.diagramType==='usecase'){ for(const a of d.actors) if(Math.abs(x-a.x)<25&&Math.abs(y-a.y)<45) return {...a,elType:'actor'}; for(const u of d.useCases){ const dx=(x-u.x)/(u.radius*1.5),dy=(y-u.y)/u.radius; if(dx*dx+dy*dy<=1) return {...u,elType:'useCase'}; } if(d.systemBoundary){ const s=d.systemBoundary; if(x>=s.x&&x<=s.x+s.width&&y>=s.y&&y<=s.y+s.height) return {...s,elType:'systemBoundary'}; } }
    else if(d.diagramType==='statemachine'){ for(const s of (d.states||[])){ if(s.stateType==='initial'){ if(Math.hypot(x-s.x,y-s.y)<=14) return {...s,elType:'initialState'}; } else if(s.stateType==='final'){ if(Math.hypot(x-s.x,y-s.y)<=18) return {...s,elType:'finalState'}; } else if(s.stateType==='state'){ const w=(s.width||120)/2,h=(s.height||50)/2; if(x>=s.x-w&&x<=s.x+w&&y>=s.y-h&&y<=s.y+h) return {...s,elType:'state'}; } else if(s.stateType==='composite'){ const w=(s.width||180)/2,h=(s.height||120)/2; if(x>=s.x-w&&x<=s.x+w&&y>=s.y-h&&y<=s.y+h) return {...s,elType:'compositeState'}; } } }
    else if(d.diagramType==='activity'){ for(const n of (d.nodes||[])){ if(n.nodeType==='initialNode'||n.nodeType==='finalNode'){ if(Math.hypot(x-n.x,y-n.y)<=18) return {...n,elType:n.nodeType}; } else if(n.nodeType==='action'){ const w=(n.width||120)/2,h=(n.height||50)/2; if(x>=n.x-w&&x<=n.x+w&&y>=n.y-h&&y<=n.y+h) return {...n,elType:'action'}; } else if(n.nodeType==='decision'||n.nodeType==='merge'){ const s=n.size||40; const dx=Math.abs(x-n.x),dy=Math.abs(y-n.y); if(dx+dy<=s) return {...n,elType:n.nodeType}; } else if(n.nodeType==='forkJoin'){ const w=n.orientation==='horizontal'?(n.width||150)/2:3, h=n.orientation==='horizontal'?3:(n.height||150)/2; if(x>=n.x-w&&x<=n.x+w&&y>=n.y-h&&y<=n.y+h) return {...n,elType:'forkJoin'}; } } }
    else if(d.diagramType==='component'){ for(const comp of (d.components||[])){ const w=(comp.width||140)/2, h=(comp.height||70)/2; if(x>=comp.x-w&&x<=comp.x+w&&y>=comp.y-h&&y<=comp.y+h) return {...comp,elType:'component'}; } for(const iface of (d.interfaces||[])){ if(Math.hypot(x-iface.x,y-iface.y)<=15) return {...iface,elType:iface.ifaceType==='provided'?'interfaceProv':'interfaceReq'}; } for(const pkg of (d.packages||[])){ const w=pkg.width||200, h=pkg.height||150; if(x>=pkg.x&&x<=pkg.x+w&&y>=pkg.y-18&&y<=pkg.y+h) return {...pkg,elType:'package'}; } }
    else if(d.diagramType==='deployment'){ for(const art of (d.artifacts||[])){ const w=(art.width||120)/2, h=(art.height||50)/2; if(x>=art.x-w&&x<=art.x+w&&y>=art.y-h&&y<=art.y+h) return {...art,elType:'artifact'}; } for(const n of (d.deployNodes||[])){ const w=(n.width||160)/2, h=(n.height||100)/2; if(x>=n.x-w&&x<=n.x+w&&y>=n.y-h&&y<=n.y+h) return {...n,elType:n.nodeType}; } }
    else if(d.diagramType==='class'){ for(const cls of (d.classes||[])){ const w=(cls.width||180)/2, h=(cls._computedH||100)/2; if(x>=cls.x-w&&x<=cls.x+w&&y>=cls.y-h&&y<=cls.y+h) return {...cls,elType:cls.classType}; } }
    else if(d.diagramType==='sequence'){ for(const p of (d.participants||[])){ const w=(p.width||120)/2, h=20; if(x>=p.x-w&&x<=p.x+w&&y>=p.y-h&&y<=p.y+h) return {...p,elType:'participant'}; } for(const a of (d.activations||[])){ const part=(d.participants||[]).find(p=>p.id===a.participantId); if(part){ const h=a.height||60; if(x>=part.x-6&&x<=part.x+6&&y>=a.y&&y<=a.y+h) return {...a,elType:'activation'}; } } for(const f of (d.fragments||[])){ const w=f.width||300, h=f.height||150; if(x>=f.x&&x<=f.x+w&&y>=f.y&&y<=f.y+h) return {...f,elType:'fragment'}; } }
    else if(d.diagramType==='swimlane'){
        for(const sw of (d.swimlanes||[])){
            const w=sw.width||400, h=sw.height||300;
            if(x>=sw.x&&x<=sw.x+w&&y>=sw.y&&y<=sw.y+h) return {...sw,elType:'swimlane'};
        }
        for(const n of (d.swNodes||[])){
            if(n.nodeType==='swInitial'||n.nodeType==='swFinal'){ if(Math.hypot(x-n.x,y-n.y)<=18) return {...n,elType:n.nodeType}; }
            else if(n.nodeType==='swAction'){ const w=(n.width||120)/2,h=(n.height||50)/2; if(x>=n.x-w&&x<=n.x+w&&y>=n.y-h&&y<=n.y+h) return {...n,elType:'swAction'}; }
            else if(n.nodeType==='swDecision'){ const s=n.size||40; const dx=Math.abs(x-n.x),dy=Math.abs(y-n.y); if(dx+dy<=s) return {...n,elType:'swDecision'}; }
            else if(n.nodeType==='swForkJoin'){ const w=n.orientation==='horizontal'?(n.width||150)/2:3, h=n.orientation==='horizontal'?3:(n.height||150)/2; if(x>=n.x-w&&x<=n.x+w&&y>=n.y-h&&y<=n.y+h) return {...n,elType:'swForkJoin'}; }
        }
    }
    return null;
}

// ===== CANVAS EVENTS =====
function getPos(e){
    const r=canvas.getBoundingClientRect();
    const sx = (e.clientX-r.left)*(canvas.width/r.width);
    const sy = (e.clientY-r.top)*(canvas.height/r.height);
    return screenToWorld(sx, sy);
}

canvas.addEventListener('mousedown',e=>{
    const d=getActive(); if(!d||canvas.width<=0) return;
    const {x,y}=getPos(e);
    const tool=state.currentTool;

    // Pan with middle mouse or space+left click
    if(e.button === 1 || (e.button === 0 && state.spacePressed)){
        state.isPanning = true;
        state.panStart = { x: e.clientX - state.panX, y: e.clientY - state.panY };
        canvas.style.cursor = 'grabbing';
        return;
    }

    if(tool==='select'){
        const el=hitTest(x,y);
        
        if(el&&el.elType==='swimlane'){
            const handles = getResizeHandles(el);
            for(const h of handles){
                if(Math.abs(x-h.x)<=6&&Math.abs(y-h.y)<=6){
                    state.resizingSwimlane = el;
                    state.resizeHandle = h.pos;
                    state.dragStart = {x, y, elX:el.x, elY:el.y, elW:el.width||400, elH:el.height||300};
                    return;
                }
            }
        }
        
        state.selectedElement=el; updatePropsPanel();
        if(el){state.isDragging=true;state.dragStart={x,y,elX:el.x||0,elY:el.y||0};}
    }
    else if(tool==='actor'){ d.actors.push({id:genElId(d),name:'Actor',x,y}); pushHistory(d); updateInfo(); draw(); switchToSelect(); return; }
    else if(tool==='useCase'){ d.useCases.push({id:genElId(d),name:'Use Case',x,y,radius:40}); pushHistory(d); updateInfo(); draw(); switchToSelect(); return; }
    else if(tool==='boundary'){ if(!d.systemBoundary){d.systemBoundary={id:genElId(d),name:'System',x:x-150,y:y-100,width:300,height:250};pushHistory(d);updateInfo();draw();switchToSelect();return;} }
    else if(tool==='initialState'){ d.states.push({id:genElId(d),stateType:'initial',name:'',x,y}); pushHistory(d); updateInfo(); draw(); switchToSelect(); return; }
    else if(tool==='state'){ d.states.push({id:genElId(d),stateType:'state',name:'State',x,y,width:120,height:50}); pushHistory(d); updateInfo(); draw(); switchToSelect(); return; }
    else if(tool==='finalState'){ d.states.push({id:genElId(d),stateType:'final',name:'',x,y}); pushHistory(d); updateInfo(); draw(); switchToSelect(); return; }
    else if(tool==='compositeState'){ d.states.push({id:genElId(d),stateType:'composite',name:'Composite',x,y,width:180,height:120}); pushHistory(d); updateInfo(); draw(); switchToSelect(); return; }
    else if(tool==='initialNode'){ d.nodes.push({id:genElId(d),nodeType:'initialNode',name:'',x,y}); pushHistory(d); updateInfo(); draw(); switchToSelect(); return; }
    else if(tool==='finalNode'){ d.nodes.push({id:genElId(d),nodeType:'finalNode',name:'',x,y}); pushHistory(d); updateInfo(); draw(); switchToSelect(); return; }
    else if(tool==='action'){ d.nodes.push({id:genElId(d),nodeType:'action',name:'Action',x,y,width:120,height:50}); pushHistory(d); updateInfo(); draw(); switchToSelect(); return; }
    else if(tool==='decision'){ d.nodes.push({id:genElId(d),nodeType:'decision',name:'',x,y,size:40}); pushHistory(d); updateInfo(); draw(); switchToSelect(); return; }
    else if(tool==='merge'){ d.nodes.push({id:genElId(d),nodeType:'merge',name:'',x,y,size:40}); pushHistory(d); updateInfo(); draw(); switchToSelect(); return; }
    else if(tool==='forkJoin'){ d.nodes.push({id:genElId(d),nodeType:'forkJoin',name:'',x,y,width:150,height:150,orientation:'horizontal'}); pushHistory(d); updateInfo(); draw(); switchToSelect(); return; }
    else if(tool==='component'){ d.components.push({id:genElId(d),name:'Component',x,y,width:140,height:70}); pushHistory(d); updateInfo(); draw(); switchToSelect(); return; }
    else if(tool==='interfaceProv'){ d.interfaces.push({id:genElId(d),ifaceType:'provided',name:'IProvided',x,y,direction:'right'}); pushHistory(d); updateInfo(); draw(); switchToSelect(); return; }
    else if(tool==='interfaceReq'){ d.interfaces.push({id:genElId(d),ifaceType:'required',name:'IRequired',x,y,direction:'right'}); pushHistory(d); updateInfo(); draw(); switchToSelect(); return; }
    else if(tool==='package'){ d.packages.push({id:genElId(d),name:'Package',x:x-100,y:y-90,width:200,height:150}); pushHistory(d); updateInfo(); draw(); switchToSelect(); return; }
    else if(tool==='node'){ d.deployNodes.push({id:genElId(d),nodeType:'node',name:'Node',x:x-80,y:y-50,width:160,height:100}); pushHistory(d); updateInfo(); draw(); switchToSelect(); return; }
    else if(tool==='device'){ d.deployNodes.push({id:genElId(d),nodeType:'device',name:'Device',x:x-80,y:y-50,width:160,height:100}); pushHistory(d); updateInfo(); draw(); switchToSelect(); return; }
    else if(tool==='execEnv'){ d.deployNodes.push({id:genElId(d),nodeType:'execEnv',name:'ExecEnv',x:x-80,y:y-50,width:160,height:100}); pushHistory(d); updateInfo(); draw(); switchToSelect(); return; }
    else if(tool==='artifact'){ d.artifacts.push({id:genElId(d),name:'Artifact',x,y,width:120,height:50}); pushHistory(d); updateInfo(); draw(); switchToSelect(); return; }
    else if(tool==='class'){ d.classes.push({id:genElId(d),classType:'class',name:'NewClass',x,y,width:180,attributes:[],methods:[]}); pushHistory(d); updateInfo(); draw(); switchToSelect(); return; }
    else if(tool==='interfaceClass'){ d.classes.push({id:genElId(d),classType:'interface',name:'INewInterface',x,y,width:180,attributes:[],methods:[]}); pushHistory(d); updateInfo(); draw(); switchToSelect(); return; }
    else if(tool==='abstractClass'){ d.classes.push({id:genElId(d),classType:'abstract',name:'AbstractClass',x,y,width:180,attributes:[],methods:[]}); pushHistory(d); updateInfo(); draw(); switchToSelect(); return; }
    else if(tool==='enumeration'){ d.classes.push({id:genElId(d),classType:'enumeration',name:'Enum',x,y,width:180,attributes:[],methods:[]}); pushHistory(d); updateInfo(); draw(); switchToSelect(); return; }
    else if(tool==='participant'){ d.participants.push({id:genElId(d),name:'Participant',x,y:60,width:120}); pushHistory(d); updateInfo(); draw(); switchToSelect(); return; }
    else if(tool==='activation'){
        let nearest=null, minDist=Infinity;
        (d.participants||[]).forEach(p=>{ const dist=Math.abs(x-p.x); if(dist<minDist){minDist=dist;nearest=p;} });
        if(nearest && minDist<50){
            d.activations.push({id:genElId(d),participantId:nearest.id,x:nearest.x,y:y,width:12,height:60});
            pushHistory(d); updateInfo(); draw(); switchToSelect(); return;
        }
    }
    else if(tool==='fragment'){
        const fragType = prompt('نوع Fragment (alt/opt/loop/break):','alt') || 'alt';
        if(['alt','opt','loop','break'].includes(fragType)){
            d.fragments.push({id:genElId(d),fragmentType:fragType,label:'',x,y,width:300,height:150,operands:[{label:'',height:70},{label:'',height:70}]});
            pushHistory(d); updateInfo(); draw(); switchToSelect(); return;
        }
    }
    else if(tool==='swimlane'){ d.swimlanes.push({id:genElId(d),name:'Swimlane',x:x-200,y:y-150,width:400,height:300,orientation:'vertical',color:null}); pushHistory(d); updateInfo(); draw(); switchToSelect(); return; }
    else if(tool==='swAction'){ d.swNodes.push({id:genElId(d),nodeType:'swAction',name:'Action',x,y,width:120,height:50}); pushHistory(d); updateInfo(); draw(); switchToSelect(); return; }
    else if(tool==='swDecision'){ d.swNodes.push({id:genElId(d),nodeType:'swDecision',name:'',x,y,size:40}); pushHistory(d); updateInfo(); draw(); switchToSelect(); return; }
    else if(tool==='swForkJoin'){ d.swNodes.push({id:genElId(d),nodeType:'swForkJoin',name:'',x,y,width:150,height:150,orientation:'horizontal'}); pushHistory(d); updateInfo(); draw(); switchToSelect(); return; }
    else if(tool==='swInitial'){ d.swNodes.push({id:genElId(d),nodeType:'swInitial',name:'',x,y}); pushHistory(d); updateInfo(); draw(); switchToSelect(); return; }
    else if(tool==='swFinal'){ d.swNodes.push({id:genElId(d),nodeType:'swFinal',name:'',x,y}); pushHistory(d); updateInfo(); draw(); switchToSelect(); return; }
    else if(['association','include','extend','generalization','transition','controlFlow','objectFlow','dependency','connector','compAssociation','commPath','deployRel','manifestRel','clsAssociation','aggregation','composition','inheritance','clsDependency','realization','syncMsg','asyncMsg','returnMsg','selfMsg','swFlow'].includes(tool)){
        const el=hitTest(x,y);
        if(el){state.isDrawingRelationship=true;state.relationshipStart=elCenter(el);state.relationshipStartId=el.id;}
    }
    draw();
});

canvas.addEventListener('mousemove',e=>{
    const d=getActive(); if(!d) return;
    const {x,y}=getPos(e); state.mousePos={x,y};
    
    // Panning
    if(state.isPanning){
        state.panX = e.clientX - state.panStart.x;
        state.panY = e.clientY - state.panStart.y;
        draw();
        return;
    }
    
    if(state.resizingSwimlane){
        const dx=x-state.dragStart.x, dy=y-state.dragStart.y;
        const sw=state.resizingSwimlane;
        const i=(d.swimlanes||[]).findIndex(s=>s.id===sw.id);
        if(i>=0){
            const handle = state.resizeHandle;
            let newX=state.dragStart.elX, newY=state.dragStart.elY, newW=state.dragStart.elW, newH=state.dragStart.elH;
            
            if(handle.includes('r')) newW = Math.max(100, state.dragStart.elW + dx);
            if(handle.includes('l')){ newX = state.dragStart.elX + dx; newW = Math.max(100, state.dragStart.elW - dx); }
            if(handle.includes('b')) newH = Math.max(100, state.dragStart.elH + dy);
            if(handle.includes('t')){ newY = state.dragStart.elY + dy; newH = Math.max(100, state.dragStart.elH - dy); }
            
            d.swimlanes[i].x = newX;
            d.swimlanes[i].y = newY;
            d.swimlanes[i].width = newW;
            d.swimlanes[i].height = newH;
        }
        draw();
        return;
    }
    
    if(state.isDragging&&state.selectedElement){
        const dx=x-state.dragStart.x, dy=y-state.dragStart.y;
        const el=state.selectedElement;
        if(d.diagramType==='usecase'){
            if(el.elType==='actor'){const i=d.actors.findIndex(a=>a.id===el.id);if(i>=0){d.actors[i].x=state.dragStart.elX+dx;d.actors[i].y=state.dragStart.elY+dy;}}
            else if(el.elType==='useCase'){const i=d.useCases.findIndex(u=>u.id===el.id);if(i>=0){d.useCases[i].x=state.dragStart.elX+dx;d.useCases[i].y=state.dragStart.elY+dy;}}
            else if(el.elType==='systemBoundary'){d.systemBoundary.x=state.dragStart.elX+dx;d.systemBoundary.y=state.dragStart.elY+dy;}
        } else if(d.diagramType==='statemachine'){ const i=(d.states||[]).findIndex(s=>s.id===el.id); if(i>=0){d.states[i].x=state.dragStart.elX+dx;d.states[i].y=state.dragStart.elY+dy;} }
        else if(d.diagramType==='activity'){ const i=(d.nodes||[]).findIndex(n=>n.id===el.id); if(i>=0){d.nodes[i].x=state.dragStart.elX+dx;d.nodes[i].y=state.dragStart.elY+dy;} }
        else if(d.diagramType==='component'){ if(el.elType==='component'){const i=d.components.findIndex(c=>c.id===el.id);if(i>=0){d.components[i].x=state.dragStart.elX+dx;d.components[i].y=state.dragStart.elY+dy;}} else if(el.elType==='interfaceProv'||el.elType==='interfaceReq'){const i=d.interfaces.findIndex(iface=>iface.id===el.id);if(i>=0){d.interfaces[i].x=state.dragStart.elX+dx;d.interfaces[i].y=state.dragStart.elY+dy;}} else if(el.elType==='package'){const i=d.packages.findIndex(p=>p.id===el.id);if(i>=0){d.packages[i].x=state.dragStart.elX+dx;d.packages[i].y=state.dragStart.elY+dy;}} }
        else if(d.diagramType==='deployment'){ if(el.elType==='node'||el.elType==='device'||el.elType==='execEnv'){const i=d.deployNodes.findIndex(n=>n.id===el.id);if(i>=0){d.deployNodes[i].x=state.dragStart.elX+dx;d.deployNodes[i].y=state.dragStart.elY+dy;}} else if(el.elType==='artifact'){const i=d.artifacts.findIndex(a=>a.id===el.id);if(i>=0){d.artifacts[i].x=state.dragStart.elX+dx;d.artifacts[i].y=state.dragStart.elY+dy;}} }
        else if(d.diagramType==='class'){ const i=(d.classes||[]).findIndex(cls=>cls.id===el.id); if(i>=0){d.classes[i].x=state.dragStart.elX+dx;d.classes[i].y=state.dragStart.elY+dy;} }
        else if(d.diagramType==='sequence'){
            if(el.elType==='participant'){const i=d.participants.findIndex(p=>p.id===el.id);if(i>=0){d.participants[i].x=state.dragStart.elX+dx;d.participants[i].y=state.dragStart.elY+dy;}}
            else if(el.elType==='activation'){const i=d.activations.findIndex(a=>a.id===el.id);if(i>=0){d.activations[i].y=state.dragStart.elY+dy;}}
            else if(el.elType==='fragment'){const i=d.fragments.findIndex(f=>f.id===el.id);if(i>=0){d.fragments[i].x=state.dragStart.elX+dx;d.fragments[i].y=state.dragStart.elY+dy;}}
        }
        else if(d.diagramType==='swimlane'){
            if(el.elType==='swimlane'){const i=d.swimlanes.findIndex(s=>s.id===el.id);if(i>=0){d.swimlanes[i].x=state.dragStart.elX+dx;d.swimlanes[i].y=state.dragStart.elY+dy;}}
            else {const i=(d.swNodes||[]).findIndex(n=>n.id===el.id);if(i>=0){d.swNodes[i].x=state.dragStart.elX+dx;d.swNodes[i].y=state.dragStart.elY+dy;}}
        }
        updatePropsPanel(); draw();
    } else if(state.isDrawingRelationship) draw();
});

canvas.addEventListener('mouseup',e=>{
    const d=getActive(); if(!d) return;
    
    // End panning
    if(state.isPanning){
        state.isPanning = false;
        canvas.style.cursor = 'crosshair';
        return;
    }
    
    const {x,y}=getPos(e);
    
    if(state.resizingSwimlane){
        pushHistory(d);
        state.resizingSwimlane = null;
        state.resizeHandle = null;
        return;
    }
    
    if(state.isDrawingRelationship){
        const el=hitTest(x,y);
        if(el&&el.id!==state.relationshipStartId){
            let changed = false;
            if(state.currentTool==='transition'){ const label = prompt('برچسب Transition (اختیاری):','') || ''; d.transitions.push({id:genElId(d),from:state.relationshipStartId,to:el.id,label:label}); changed=true; }
            else if(state.currentTool==='controlFlow'||state.currentTool==='objectFlow'){ const label = prompt('برچسب Flow (اختیاری):','') || ''; d.flows.push({id:genElId(d),from:state.relationshipStartId,to:el.id,flowType:state.currentTool,label:label}); changed=true; }
            else if(state.currentTool==='dependency'||state.currentTool==='connector'||state.currentTool==='compAssociation'){ const label = prompt('برچسب رابطه (اختیاری):','') || ''; d.compRelations.push({id:genElId(d),from:state.relationshipStartId,to:el.id,relType:state.currentTool,label:label}); changed=true; }
            else if(state.currentTool==='commPath'||state.currentTool==='deployRel'||state.currentTool==='manifestRel'){ const label = prompt('برچسب رابطه (اختیاری):','') || ''; d.deployRelations.push({id:genElId(d),from:state.relationshipStartId,to:el.id,relType:state.currentTool,label:label}); changed=true; }
            else if(state.currentTool==='clsAssociation'||state.currentTool==='aggregation'||state.currentTool==='composition'||state.currentTool==='inheritance'||state.currentTool==='clsDependency'||state.currentTool==='realization'){
                const label = prompt('برچسب رابطه (اختیاری):','') || '';
                const fromMult = prompt('Multiplicity مبدأ (مثال: 1, 0..*, 1..*):','') || '';
                const toMult = prompt('Multiplicity مقصد (مثال: 1, 0..*, 1..*):','') || '';
                d.classRelations.push({id:genElId(d),from:state.relationshipStartId,to:el.id,relType:state.currentTool,label:label,fromMultiplicity:fromMult,toMultiplicity:toMult});
                changed=true;
            }
            else if(state.currentTool==='syncMsg'||state.currentTool==='asyncMsg'||state.currentTool==='returnMsg'){
                const label = prompt('برچسب پیام (مثال: doSomething()):','') || '';
                d.messages.push({id:genElId(d),fromParticipant:state.relationshipStartId,toParticipant:el.id,msgType:state.currentTool,label:label,y:y});
                changed=true;
            }
            else if(state.currentTool==='selfMsg'){
                const label = prompt('برچسب پیام خودی:','') || '';
                d.messages.push({id:genElId(d),fromParticipant:state.relationshipStartId,toParticipant:state.relationshipStartId,msgType:'selfMsg',label:label,y:y});
                changed=true;
            }
            else if(state.currentTool==='swFlow'){
                const label = prompt('برچسب Flow (اختیاری):','') || '';
                d.swFlows.push({id:genElId(d),from:state.relationshipStartId,to:el.id,label:label});
                changed=true;
            }
            else { d.relationships.push({id:genElId(d),from:state.relationshipStartId,to:el.id,type:state.currentTool}); changed=true; }
            if(changed) pushHistory(d);
            updateInfo(); draw(); switchToSelect();
        }
        state.isDrawingRelationship=false; state.relationshipStart=null; state.relationshipStartId=null;
    }
    if(state.isDragging && state.selectedElement){
        pushHistory(d);
    }
    state.isDragging=false; draw();
});

canvas.addEventListener('dblclick', e=>{
    const d=getActive(); if(!d) return;
    const {x,y}=getPos(e);
    const el=hitTest(x,y);
    if(!el) return;
    
    if((el.elType==='interfaceProv'||el.elType==='interfaceReq') && d.diagramType==='component'){
        const i=d.interfaces.findIndex(iface=>iface.id===el.id);
        if(i>=0){ const dirs=['right','down','left','up']; const cur=d.interfaces[i].direction||'right'; d.interfaces[i].direction = dirs[(dirs.indexOf(cur)+1)%4]; pushHistory(d); draw(); }
    }
    else if((el.elType==='class'||el.elType==='interfaceClass'||el.elType==='abstractClass'||el.elType==='enumeration') && d.diagramType==='class'){
        openClassEditor(el.id);
    }
    else if(el.elType==='swimlane' && d.diagramType==='swimlane'){
        const i=d.swimlanes.findIndex(s=>s.id===el.id);
        if(i>=0){
            d.swimlanes[i].orientation = d.swimlanes[i].orientation==='vertical' ? 'horizontal' : 'vertical';
            pushHistory(d);
            draw();
        }
    }
});

// Zoom with mouse wheel (Ctrl + Wheel)
canvas.addEventListener('wheel', e=>{
    e.preventDefault();
    if(e.ctrlKey || e.metaKey){
        const rect = canvas.getBoundingClientRect();
        const centerX = (e.clientX - rect.left) * (canvas.width / rect.width);
        const centerY = (e.clientY - rect.top) * (canvas.height / rect.height);
        const delta = e.deltaY > 0 ? 0.9 : 1.1;
        setZoom(state.zoom * delta, centerX, centerY);
    }
}, {passive: false});

// ===== CLASS EDITOR =====
const classEditorModal = document.getElementById('classEditorModal');
let classEditorData = { attributes: [], methods: [] };
let activeEditorTab = 'attributes';

function openClassEditor(classId){
    const d=getActive(); if(!d) return;
    const cls = (d.classes||[]).find(c=>c.id===classId);
    if(!cls) return;
    
    state.editingClassId = classId;
    document.getElementById('classEditorName').value = cls.name || '';
    classEditorData = {
        attributes: JSON.parse(JSON.stringify(cls.attributes||[])),
        methods: JSON.parse(JSON.stringify(cls.methods||[]))
    };
    
    activeEditorTab = 'attributes';
    updateEditorTabsUI();
    renderAttributesTable();
    renderMethodsTable();
    
    classEditorModal.classList.add('visible');
}

function closeClassEditor(){
    classEditorModal.classList.remove('visible');
    state.editingClassId = null;
}

function updateEditorTabsUI(){
    document.querySelectorAll('.class-editor-tab').forEach(t=>{
        t.classList.toggle('active', t.dataset.tab===activeEditorTab);
    });
    document.getElementById('attributesTab').style.display = activeEditorTab==='attributes'?'':'none';
    document.getElementById('methodsTab').style.display = activeEditorTab==='methods'?'':'none';
}

document.querySelectorAll('.class-editor-tab').forEach(t=>{
    t.addEventListener('click', ()=>{
        activeEditorTab = t.dataset.tab;
        updateEditorTabsUI();
    });
});

function renderAttributesTable(){
    const tbody = document.getElementById('attributesTableBody');
    tbody.innerHTML = '';
    classEditorData.attributes.forEach((attr, idx)=>{
        const tr = document.createElement('tr');
        tr.innerHTML = `
            <td>
                <select data-idx="${idx}" data-field="visibility">
                    <option value="+" ${attr.visibility==='+'?'selected':''}>+ public</option>
                    <option value="-" ${attr.visibility==='-'?'selected':''}>- private</option>
                    <option value="#" ${attr.visibility==='#'?'selected':''}># protected</option>
                    <option value="~" ${attr.visibility==='~'?'selected':''}>~ package</option>
                </select>
            </td>
            <td><input type="text" value="${attr.name||''}" data-idx="${idx}" data-field="name" placeholder="نام"></td>
            <td><input type="text" value="${attr.type||''}" data-idx="${idx}" data-field="type" placeholder="نوع"></td>
            <td style="text-align:center"><input type="checkbox" ${attr.isStatic?'checked':''} data-idx="${idx}" data-field="isStatic"></td>
            <td><button class="member-btn member-btn-del" data-idx="${idx}" data-action="del-attr">×</button></td>
        `;
        tbody.appendChild(tr);
    });
    
    tbody.querySelectorAll('select, input').forEach(el=>{
        el.addEventListener('change', e=>{
            const idx = parseInt(e.target.dataset.idx);
            const field = e.target.dataset.field;
            if(field==='isStatic'){
                classEditorData.attributes[idx].isStatic = e.target.checked;
            } else {
                classEditorData.attributes[idx][field] = e.target.value;
            }
        });
    });
    tbody.querySelectorAll('[data-action="del-attr"]').forEach(btn=>{
        btn.addEventListener('click', e=>{
            const idx = parseInt(e.target.dataset.idx);
            classEditorData.attributes.splice(idx, 1);
            renderAttributesTable();
        });
    });
}

function renderMethodsTable(){
    const tbody = document.getElementById('methodsTableBody');
    tbody.innerHTML = '';
    classEditorData.methods.forEach((meth, idx)=>{
        const tr = document.createElement('tr');
        tr.innerHTML = `
            <td>
                <select data-idx="${idx}" data-field="visibility">
                    <option value="+" ${meth.visibility==='+'?'selected':''}>+ public</option>
                    <option value="-" ${meth.visibility==='-'?'selected':''}>- private</option>
                    <option value="#" ${meth.visibility==='#'?'selected':''}># protected</option>
                    <option value="~" ${meth.visibility==='~'?'selected':''}>~ package</option>
                </select>
            </td>
            <td><input type="text" value="${meth.name||''}" data-idx="${idx}" data-field="name" placeholder="نام"></td>
            <td><input type="text" value="${meth.params||''}" data-idx="${idx}" data-field="params" placeholder="x: int, y: string"></td>
            <td><input type="text" value="${meth.returnType||''}" data-idx="${idx}" data-field="returnType" placeholder="void"></td>
            <td style="text-align:center"><input type="checkbox" ${meth.isStatic?'checked':''} data-idx="${idx}" data-field="isStatic"></td>
            <td><button class="member-btn member-btn-del" data-idx="${idx}" data-action="del-meth">×</button></td>
        `;
        tbody.appendChild(tr);
    });
    
    tbody.querySelectorAll('select, input').forEach(el=>{
        el.addEventListener('change', e=>{
            const idx = parseInt(e.target.dataset.idx);
            const field = e.target.dataset.field;
            if(field==='isStatic'){
                classEditorData.methods[idx].isStatic = e.target.checked;
            } else {
                classEditorData.methods[idx][field] = e.target.value;
            }
        });
    });
    tbody.querySelectorAll('[data-action="del-meth"]').forEach(btn=>{
        btn.addEventListener('click', e=>{
            const idx = parseInt(e.target.dataset.idx);
            classEditorData.methods.splice(idx, 1);
            renderMethodsTable();
        });
    });
}

document.getElementById('addAttrBtn').addEventListener('click', ()=>{
    classEditorData.attributes.push({visibility:'+',name:'newAttr',type:'String',isStatic:false});
    renderAttributesTable();
});
document.getElementById('addMethodBtn').addEventListener('click', ()=>{
    classEditorData.methods.push({visibility:'+',name:'newMethod',params:'',returnType:'void',isStatic:false});
    renderMethodsTable();
});

document.getElementById('closeClassEditor').addEventListener('click', closeClassEditor);
document.getElementById('cancelClassEditor').addEventListener('click', closeClassEditor);
classEditorModal.addEventListener('click', e=>{ if(e.target===classEditorModal) closeClassEditor(); });

document.getElementById('saveClassEditor').addEventListener('click', ()=>{
    const d=getActive(); if(!d) return;
    const cls = (d.classes||[]).find(c=>c.id===state.editingClassId);
    if(!cls) return;
    
    cls.name = document.getElementById('classEditorName').value.trim() || 'Unnamed';
    cls.attributes = classEditorData.attributes;
    cls.methods = classEditorData.methods;
    
    pushHistory(d);
    closeClassEditor();
    draw();
    updateStatus('✅ Class ویرایش شد');
});

// ===== PROPERTIES INPUT =====
document.getElementById('elementName').addEventListener('input',e=>{
    if(!state.selectedElement) return;
    const d=getActive(); if(!d) return;
    const id=state.selectedElement.id, v=e.target.value;
    if(d.diagramType==='usecase'){ if(state.selectedElement.elType==='actor'){const i=d.actors.findIndex(a=>a.id===id);if(i>=0)d.actors[i].name=v;} else if(state.selectedElement.elType==='useCase'){const i=d.useCases.findIndex(u=>u.id===id);if(i>=0)d.useCases[i].name=v;} else if(state.selectedElement.elType==='systemBoundary') d.systemBoundary.name=v; }
    else if(d.diagramType==='statemachine'){ const i=(d.states||[]).findIndex(s=>s.id===id); if(i>=0) d.states[i].name=v; }
    else if(d.diagramType==='activity'){ const i=(d.nodes||[]).findIndex(n=>n.id===id); if(i>=0) d.nodes[i].name=v; }
    else if(d.diagramType==='component'){ if(state.selectedElement.elType==='component'){const i=d.components.findIndex(c=>c.id===id);if(i>=0)d.components[i].name=v;} else if(state.selectedElement.elType==='interfaceProv'||state.selectedElement.elType==='interfaceReq'){const i=d.interfaces.findIndex(iface=>iface.id===id);if(i>=0)d.interfaces[i].name=v;} else if(state.selectedElement.elType==='package'){const i=d.packages.findIndex(p=>p.id===id);if(i>=0)d.packages[i].name=v;} }
    else if(d.diagramType==='deployment'){ if(state.selectedElement.elType==='node'||state.selectedElement.elType==='device'||state.selectedElement.elType==='execEnv'){const i=d.deployNodes.findIndex(n=>n.id===id);if(i>=0)d.deployNodes[i].name=v;} else if(state.selectedElement.elType==='artifact'){const i=d.artifacts.findIndex(a=>a.id===id);if(i>=0)d.artifacts[i].name=v;} }
    else if(d.diagramType==='class'){ const i=(d.classes||[]).findIndex(cls=>cls.id===id); if(i>=0) d.classes[i].name=v; }
    else if(d.diagramType==='sequence'){ if(state.selectedElement.elType==='participant'){const i=d.participants.findIndex(p=>p.id===id);if(i>=0)d.participants[i].name=v;} }
    else if(d.diagramType==='swimlane'){
        if(state.selectedElement.elType==='swimlane'){const i=d.swimlanes.findIndex(s=>s.id===id);if(i>=0)d.swimlanes[i].name=v;}
        else {const i=(d.swNodes||[]).findIndex(n=>n.id===id);if(i>=0)d.swNodes[i].name=v;}
    }
    draw();
});

// ===== ACTION BUTTONS =====
document.getElementById('deleteBtn').addEventListener('click',()=>{
    if(!state.selectedElement) return;
    const d=getActive(); if(!d) return;
    const {id,elType}=state.selectedElement;
    if(d.diagramType==='usecase'){ if(elType==='actor') d.actors=d.actors.filter(a=>a.id!==id); else if(elType==='useCase') d.useCases=d.useCases.filter(u=>u.id!==id); else if(elType==='systemBoundary') d.systemBoundary=null; d.relationships=d.relationships.filter(r=>r.from!==id&&r.to!==id); }
    else if(d.diagramType==='statemachine'){ d.states=(d.states||[]).filter(s=>s.id!==id); d.transitions=(d.transitions||[]).filter(t=>t.from!==id&&t.to!==id); }
    else if(d.diagramType==='activity'){ d.nodes=(d.nodes||[]).filter(n=>n.id!==id); d.flows=(d.flows||[]).filter(f=>f.from!==id&&f.to!==id); }
    else if(d.diagramType==='component'){ if(elType==='component'){ d.components=d.components.filter(c=>c.id!==id); } else if(elType==='interfaceProv'||elType==='interfaceReq'){ d.interfaces=d.interfaces.filter(i=>i.id!==id); } else if(elType==='package'){ d.packages=d.packages.filter(p=>p.id!==id); } d.compRelations=(d.compRelations||[]).filter(r=>r.from!==id&&r.to!==id); }
    else if(d.diagramType==='deployment'){ if(elType==='node'||elType==='device'||elType==='execEnv'){ d.deployNodes=d.deployNodes.filter(n=>n.id!==id); } else if(elType==='artifact'){ d.artifacts=d.artifacts.filter(a=>a.id!==id); } d.deployRelations=(d.deployRelations||[]).filter(r=>r.from!==id&&r.to!==id); }
    else if(d.diagramType==='class'){ d.classes=(d.classes||[]).filter(c=>c.id!==id); d.classRelations=(d.classRelations||[]).filter(r=>r.from!==id&&r.to!==id); }
    else if(d.diagramType==='sequence'){
        if(elType==='participant'){ d.participants=d.participants.filter(p=>p.id!==id); d.activations=d.activations.filter(a=>a.participantId!==id); d.messages=d.messages.filter(m=>m.fromParticipant!==id&&m.toParticipant!==id); }
        else if(elType==='activation'){ d.activations=d.activations.filter(a=>a.id!==id); }
        else if(elType==='fragment'){ d.fragments=d.fragments.filter(f=>f.id!==id); }
    }
    else if(d.diagramType==='swimlane'){
        if(elType==='swimlane'){ d.swimlanes=d.swimlanes.filter(s=>s.id!==id); }
        else { d.swNodes=(d.swNodes||[]).filter(n=>n.id!==id); }
        d.swFlows=(d.swFlows||[]).filter(f=>f.from!==id&&f.to!==id);
    }
    pushHistory(d);
    state.selectedElement=null; updatePropsPanel(); updateInfo(); draw();
});

document.getElementById('clearBtn').addEventListener('click',()=>{
    const d=getActive(); if(!d) return;
    if(!confirm(`پاک کردن "${d.title}"؟`)) return;
    if(d.diagramType==='usecase'){d.actors=[];d.useCases=[];d.systemBoundary=null;d.relationships=[];}
    else if(d.diagramType==='statemachine'){d.states=[];d.transitions=[];}
    else if(d.diagramType==='activity'){d.nodes=[];d.flows=[];}
    else if(d.diagramType==='component'){d.components=[];d.interfaces=[];d.packages=[];d.compRelations=[];}
    else if(d.diagramType==='deployment'){d.deployNodes=[];d.artifacts=[];d.deployRelations=[];}
    else if(d.diagramType==='class'){d.classes=[];d.classRelations=[];}
    else if(d.diagramType==='sequence'){d.participants=[];d.activations=[];d.messages=[];d.fragments=[];}
    else if(d.diagramType==='swimlane'){d.swimlanes=[];d.swNodes=[];d.swFlows=[];}
    pushHistory(d);
    state.selectedElement=null; updatePropsPanel(); updateInfo(); draw();
});

// ===== KEYBOARD =====
document.addEventListener('keydown',e=>{
    if(e.target.tagName==='INPUT'||e.target.tagName==='TEXTAREA'||e.target.tagName==='SELECT'){
        if(e.key==='Enter'&&saveModal.classList.contains('visible')&&e.target===saveFN){e.preventDefault();document.getElementById('confirmSaveBtn').click();}
        return;
    }
    if(e.key==='Delete'||e.key==='Backspace') document.getElementById('deleteBtn').click();
    else if(e.key===' '){
        e.preventDefault();
        state.spacePressed = true;
        canvas.style.cursor = 'grab';
    }
    else if(e.key==='Escape'){
        if(classEditorModal.classList.contains('visible')) closeClassEditor();
        else if(document.getElementById('newDiagramModal').classList.contains('visible')) hideNewDiagramModal();
        else if(expModal.classList.contains('visible')) hideExpPng();
        else if(saveModal.classList.contains('visible')) hideSaveModal();
        else if(fileSettingsModal.classList.contains('visible')) hideFileSettings();
        else if(settingsModal.classList.contains('visible')) hideSettings();
        else { state.selectedElement=null; updatePropsPanel(); draw(); }
    }
    else if(e.ctrlKey&&e.key==='n'){e.preventDefault();showNewDiagramModal();}
    else if(e.ctrlKey&&e.key==='s'){e.preventDefault();showSaveModal();}
    else if(e.ctrlKey&&e.key==='z'){e.preventDefault();undo();}
    else if(e.ctrlKey&&(e.key==='y'||(e.shiftKey&&e.key==='Z'))){e.preventDefault();redo();}
    else if((e.key==='b'||e.key==='B')&&!e.ctrlKey&&!e.altKey) toggleSidebar();
    else if((e.key==='g'||e.key==='G')&&!e.ctrlKey&&!e.altKey){ const o=['none','lines','dots']; state.gridType=o[(o.indexOf(state.gridType)+1)%3]; setGridUI(state.gridType); draw(); }
});

document.addEventListener('keyup',e=>{
    if(e.key===' '){
        state.spacePressed = false;
        if(!state.isPanning) canvas.style.cursor = 'crosshair';
    }
});

// ===== INIT =====
// Initialize collapsed groups (all except selection)
['uc-elements','uc-relations','sm-elements','sm-relations','act-elements','act-flows',
 'comp-elements','comp-relations','dep-elements','dep-relations','cls-elements','cls-relations',
 'seq-elements','seq-messages','sw-elements','sw-flows','display','actions','file'].forEach(g=>state.collapsedGroups.add(g));
// Apply collapsed class to all groups except selection
document.querySelectorAll('.tool-group').forEach(g=>{
    if(g.dataset.group !== 'selection'){
        g.classList.add('collapsed');
    }
});

applyTheme(); setGridUI(state.gridType); updateToolVisibility(); updateZoomDisplay();
renderTabs(); updateInfo(); updateVis(); scheduleResize();
</script>
</body>
</html>

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Resources\Tools\Document\xKanban_Case_Study_1.html

<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>xKanban | مستند جامع معرفی محصول و معماری فنی</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap');
:root {
--primary: #1a365d;
--primary-light: #2c5282;
--accent: #c9a961;
--accent-dark: #a8893f;
--bg: #faf9f6;
--card-bg: #ffffff;
--text: #1a202c;
--text-muted: #4a5568;
--border: #e2e8f0;
--success: #38a169;
--danger: #e53e3e;
--warning: #dd6b20;
--shadow: 0 4px 6px rgba(0,0,0,0.05), 0 10px 30px rgba(26,54,93,0.08);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Vazirmatn', Tahoma, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.9;
font-size: 15px;
padding: 30px 15px;
}
.document-container {
max-width: 950px;
margin: 0 auto;
background: var(--card-bg);
box-shadow: var(--shadow);
border-radius: 12px;
overflow: hidden;
}
.document-header {
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);
color: white;
padding: 50px 40px 40px;
position: relative;
border-bottom: 4px solid var(--accent);
}
.document-header::before {
content: '';
position: absolute;
top: 0; right: 0;
width: 200px; height: 200px;
background: radial-gradient(circle, rgba(201,169,97,0.15) 0%, transparent 70%);
border-radius: 50%;
transform: translate(50%, -50%);
}
.header-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
position: relative;
z-index: 1;
}
.company-badge {
display: flex;
align-items: center;
gap: 12px;
}
.company-logo {
width: 60px; height: 60px;
background: var(--accent);
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
.doc-meta {
text-align: left;
font-size: 12px;
opacity: 0.9;
}
.doc-meta div { margin-bottom: 3px; }
.document-title {
text-align: center;
position: relative;
z-index: 1;
padding: 20px 0;
}
.document-title .label {
display: inline-block;
background: rgba(201,169,97,0.2);
color: var(--accent);
padding: 4px 16px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
margin-bottom: 15px;
border: 1px solid rgba(201,169,97,0.4);
}
.document-title h1 {
font-size: 28px;
font-weight: 800;
margin-bottom: 10px;
letter-spacing: -0.5px;
}
.document-title h2 {
font-size: 16px;
font-weight: 400;
opacity: 0.9;
}
.document-body {
padding: 40px;
}
.intro-text {
background: linear-gradient(to left, #f7fafc, #edf2f7);
border-right: 4px solid var(--accent);
padding: 20px 25px;
border-radius: 8px;
margin-bottom: 35px;
font-size: 14px;
color: var(--text-muted);
}
.section {
margin-bottom: 40px;
page-break-inside: avoid;
}
.section-header {
display: flex;
align-items: center;
gap: 15px;
padding-bottom: 12px;
border-bottom: 2px solid var(--border);
margin-bottom: 20px;
}
.section-number {
width: 50px; height: 50px;
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 800;
font-size: 20px;
flex-shrink: 0;
box-shadow: 0 4px 10px rgba(26,54,93,0.2);
}
.section-title { flex: 1; }
.section-title h3 {
font-size: 19px;
font-weight: 700;
color: var(--primary);
margin-bottom: 2px;
}
.section-title .subtitle {
font-size: 12px;
color: var(--text-muted);
}
.section-content {
padding-right: 10px;
}
.section-content p {
margin-bottom: 12px;
text-align: justify;
text-indent: 20px;
}
.section-content p.no-indent { text-indent: 0; }
.highlight-box {
background: linear-gradient(to left, #fffaf0, #fef5e7);
border: 1px solid #f6e05e;
border-right: 4px solid var(--accent);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.highlight-box strong { color: var(--accent-dark); }
.info-box {
background: linear-gradient(to left, #ebf8ff, #e6f6ff);
border: 1px solid #90cdf4;
border-right: 4px solid var(--primary-light);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.success-box {
background: linear-gradient(to left, #f0fff4, #e6fffa);
border: 1px solid #9ae6b4;
border-right: 4px solid var(--success);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.warning-box {
background: linear-gradient(to left, #fffaf0, #fef5e7);
border: 1px solid #fbd38d;
border-right: 4px solid var(--warning);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.code-box {
background: #1a202c;
color: #e2e8f0;
border-right: 4px solid var(--accent);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 13px;
font-family: 'Consolas', 'Courier New', monospace;
direction: ltr;
text-align: left;
overflow-x: auto;
line-height: 1.7;
}
.code-box .key { color: #c9a961; }
.code-box .str { color: #68d391; }
.code-box .num { color: #63b3ed; }
.code-box .comment { color: #718096; font-style: italic; }
.code-box .fn { color: #f6ad55; }
.code-box .kw { color: #fc8181; }
.styled-table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
}
.styled-table thead {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
}
.styled-table th {
padding: 14px 16px;
text-align: right;
font-weight: 600;
font-size: 13px;
}
.styled-table td {
padding: 13px 16px;
border-bottom: 1px solid var(--border);
font-size: 13px;
}
.styled-table tbody tr:last-child td { border-bottom: none; }
.styled-table tbody tr:nth-child(even) { background: #f8fafc; }
.styled-table .highlight-row {
background: #fffaf0 !important;
font-weight: 600;
}
.comparison-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin: 25px 0;
}
.comparison-card {
background: #f8fafc;
border: 2px solid var(--border);
border-radius: 10px;
padding: 20px;
position: relative;
}
.comparison-card.recommended {
border-color: var(--success);
background: linear-gradient(to left, #f0fff4, #e6fffa);
}
.comparison-card .card-badge {
position: absolute;
top: -10px;
right: 20px;
background: var(--success);
color: white;
padding: 3px 12px;
border-radius: 15px;
font-size: 11px;
font-weight: 600;
}
.comparison-card h4 {
font-size: 16px;
font-weight: 700;
color: var(--primary);
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 2px solid var(--border);
}
.comparison-card.recommended h4 { border-bottom-color: var(--success); }
.comparison-card ul { list-style: none; padding: 0; }
.comparison-card ul li {
padding: 8px 0;
border-bottom: 1px solid var(--border);
font-size: 13px;
display: flex;
justify-content: space-between;
}
.comparison-card ul li:last-child { border-bottom: none; }
.comparison-card ul li .value { font-weight: 600; color: var(--primary); }
.quote-box {
background: linear-gradient(135deg, #f7fafc, #edf2f7);
border-right: 4px solid var(--accent);
padding: 20px 25px;
border-radius: 8px;
margin: 20px 0;
position: relative;
}
.quote-box::before {
content: '"';
position: absolute;
top: -10px;
right: 15px;
font-size: 60px;
color: var(--accent);
opacity: 0.3;
font-family: Georgia, serif;
}
.quote-box p {
font-style: italic;
color: var(--text-muted);
margin-bottom: 10px;
text-indent: 0 !important;
}
.quote-box .source {
font-size: 12px;
color: var(--text-muted);
text-align: left;
font-style: normal;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
margin: 25px 0;
}
.stat-card {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
padding: 20px;
border-radius: 10px;
text-align: center;
box-shadow: 0 4px 12px rgba(26,54,93,0.2);
}
.stat-card .stat-value {
font-size: 24px;
font-weight: 800;
margin-bottom: 5px;
color: var(--accent);
line-height: 1.3;
}
.stat-card .stat-label { font-size: 12px; opacity: 0.9; }
.feature-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
margin: 25px 0;
}
.feature-card {
background: #f8fafc;
border: 1px solid var(--border);
border-right: 4px solid var(--primary-light);
padding: 18px;
border-radius: 8px;
}
.feature-card .feature-icon { font-size: 24px; margin-bottom: 10px; }
.feature-card h5 {
font-size: 15px;
font-weight: 700;
color: var(--primary);
margin-bottom: 8px;
}
.feature-card p {
font-size: 13px;
color: var(--text-muted);
text-indent: 0 !important;
margin-bottom: 0 !important;
}
.architecture-diagram {
background: linear-gradient(to left, #f7fafc, #edf2f7);
border: 2px solid var(--border);
border-radius: 10px;
padding: 25px;
margin: 25px 0;
text-align: center;
}
.arch-layer {
background: white;
border: 2px solid var(--primary-light);
border-radius: 8px;
padding: 15px;
margin: 10px auto;
max-width: 750px;
box-shadow: 0 2px 6px rgba(0,0,0,0.05);
}
.arch-layer-title {
font-weight: 700;
color: var(--primary);
margin-bottom: 10px;
font-size: 14px;
border-bottom: 2px solid var(--accent);
padding-bottom: 5px;
display: inline-block;
}
.arch-components {
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: center;
}
.arch-chip {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
padding: 6px 14px;
border-radius: 15px;
font-size: 12px;
font-weight: 500;
}
.arch-chip.gold {
background: linear-gradient(135deg, var(--accent-dark), var(--accent));
color: #1a202c;
}
.arch-arrow {
text-align: center;
font-size: 24px;
color: var(--accent);
margin: 5px 0;
}
.conclusion-box {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
padding: 30px;
border-radius: 12px;
margin: 30px 0;
text-align: center;
box-shadow: 0 8px 20px rgba(26,54,93,0.3);
}
.conclusion-box h3 {
font-size: 22px;
font-weight: 800;
margin-bottom: 15px;
color: var(--accent);
}
.conclusion-box p {
font-size: 14px;
line-height: 1.8;
text-indent: 0 !important;
margin-bottom: 15px;
}
.conclusion-box .highlight {
background: rgba(201,169,97,0.2);
padding: 15px 20px;
border-radius: 8px;
margin-top: 20px;
border: 1px solid rgba(201,169,97,0.4);
}
.document-footer {
background: var(--primary);
color: white;
padding: 25px 40px;
text-align: center;
font-size: 12px;
margin-top: 40px;
}
.document-footer .footer-brand {
font-weight: 700;
color: var(--accent);
margin-bottom: 5px;
font-size: 14px;
}
.footer-divider {
width: 60px;
height: 2px;
background: var(--accent);
margin: 10px auto;
}
.page-info {
text-align: center;
padding: 15px;
font-size: 11px;
color: var(--text-muted);
border-top: 1px solid var(--border);
margin-top: 30px;
}
.toc {
background: #f8fafc;
border: 1px solid var(--border);
border-radius: 10px;
padding: 25px 30px;
margin-bottom: 35px;
}
.toc h4 {
font-size: 16px;
font-weight: 700;
color: var(--primary);
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 2px solid var(--accent);
}
.toc ol {
padding-right: 20px;
}
.toc ol li {
padding: 6px 0;
font-size: 14px;
color: var(--text-muted);
}
.toc ol li strong { color: var(--primary); }
.value-card {
background: linear-gradient(135deg, #f0fff4, #e6fffa);
border: 1px solid #9ae6b4;
border-radius: 10px;
padding: 20px;
margin-bottom: 12px;
display: flex;
gap: 15px;
align-items: flex-start;
}
.value-card .value-icon {
font-size: 28px;
flex-shrink: 0;
margin-top: 3px;
}
.value-card .value-body h5 {
font-size: 15px;
font-weight: 700;
color: var(--primary);
margin-bottom: 6px;
}
.value-card .value-body p {
font-size: 13px;
color: var(--text-muted);
text-indent: 0 !important;
margin-bottom: 0 !important;
}
.audience-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
margin: 25px 0;
}
.audience-card {
background: white;
border: 2px solid var(--border);
border-radius: 10px;
padding: 20px;
text-align: center;
transition: all 0.2s;
}
.audience-card:hover {
border-color: var(--accent);
box-shadow: 0 4px 12px rgba(201,169,97,0.15);
}
.audience-card .audience-icon { font-size: 36px; margin-bottom: 10px; }
.audience-card h5 {
font-size: 15px;
font-weight: 700;
color: var(--primary);
margin-bottom: 8px;
}
.audience-card p {
font-size: 12px;
color: var(--text-muted);
text-indent: 0 !important;
margin-bottom: 0 !important;
}
.method-list {
list-style: none;
padding: 0;
margin: 15px 0;
}
.method-list li {
background: #f8fafc;
border-right: 3px solid var(--primary-light);
padding: 10px 16px;
margin-bottom: 6px;
border-radius: 6px;
font-size: 13px;
font-family: 'Consolas', 'Courier New', monospace;
direction: ltr;
text-align: left;
display: flex;
justify-content: space-between;
align-items: center;
}
.method-list li .method-name {
color: var(--primary);
font-weight: 600;
}
.method-list li .method-desc {
font-family: 'Vazirmatn', Tahoma, sans-serif;
direction: rtl;
text-align: right;
color: var(--text-muted);
font-size: 12px;
}
@media print {
body { background: white; padding: 0; }
.document-container { box-shadow: none; border-radius: 0; }
.section { page-break-inside: avoid; }
}
@media (max-width: 768px) {
.document-body { padding: 25px 20px; }
.document-header { padding: 30px 20px; }
.comparison-grid { grid-template-columns: 1fr; }
.stats-grid { grid-template-columns: 1fr; }
.feature-grid { grid-template-columns: 1fr; }
.audience-grid { grid-template-columns: 1fr; }
.header-top { flex-direction: column; gap: 15px; }
.doc-meta { text-align: center; }
}
</style>
</head>
<body>
<div class="document-container">

<!-- ═══════════════ HEADER ═══════════════ -->
<header class="document-header">
<div class="header-top">
<div class="company-badge">
<div class="company-logo"></div>
</div>
<div class="doc-meta">
<div>شماره مستند: PROD-XKN-1405-002</div>
<div>نسخه: ۱.۰.۰</div>
<div>تاریخ تهیه: یکشنبه ۲۲ شهریور ۱۴۰۵ (۱۳ سپتامبر ۲۰۲۶)</div>
<div>ساعت: ۱۰:۳۰</div>
<div>طبقه‌بندی: عمومی - معرفی محصول</div>
</div>
</div>
<div class="document-title">
<span class="label">PRODUCT DOCUMENTATION</span>
<h1>xKanban | ابزار مدیریت بردهای کانبان</h1>
<h2>معرفی محصول، تحلیل ارزش سازمانی و معماری فنی</h2>
</div>
</header>

<!-- ═══════════════ BODY ═══════════════ -->
<div class="document-body">

<div class="intro-text">
<strong>مخاطبان مستند:</strong> مدیران فناوری اطلاعات، مدیران پروژه، سرپرستان تیم‌های توسعه، معماران نرم‌افزار و تصمیم‌گیرندگان سازمانی<br>
این مستند به معرفی جامع ابزار <strong>xKanban</strong> می‌پردازد؛ ابزاری که از دل نیازهای واقعی تیم‌های چابک متولد شده و با هدف حذف پیچیدگی‌های غیرضروری ابزارهای مدیریت پروژه طراحی شده است. در این مستند، از چرایی شکل‌گیری محصول تا معماری فنی و ارزش‌های سازمانی آن، همه‌چیز تشریح شده است.
</div>

<!-- فهرست مطالب -->
<div class="toc">
<h4>📑 فهرست مطالب</h4>
<ol>
<li><strong>شرح مسئله و چرایی خلق xKanban</strong></li>
<li><strong>مزایای کلیدی محصول</strong></li>
<li><strong>ارزش‌های سازمانی خلق‌شده</strong></li>
<li><strong>معماری فنی پروژه</strong></li>
<li><strong>مناسب برای چه سازمان‌ها و تیم‌هایی؟</strong></li>
<li><strong>جمع‌بندی و گام‌های بعدی</strong></li>
</ol>
</div>

<!-- ═══════════════ بخش ۱: شرح مسئله ═══════════════ -->
<div class="section">
<div class="section-header">
<div class="section-number">۱</div>
<div class="section-title">
<h3>شرح مسئله و چرایی خلق xKanban</h3>
<div class="subtitle">Problem Statement & Product Genesis</div>
</div>
</div>
<div class="section-content">

<p>در دنیای امروز، تقریباً هر تیمی — از توسعه نرم‌افزار گرفته تا بازاریابی، منابع انسانی و عملیات — با یک چالش مشترک دست‌وپنجه نرم می‌کند: <strong>چگونه کارها را به‌صورت شفاف، منظم و قابل ردیابی مدیریت کنیم؟</strong> متدولوژی کانبان به‌عنوان یکی از مؤثرترین رویکردهای مدیریت بصری کار، سال‌هاست که پاسخ این سؤال را ارائه داده است؛ اما ابزارهای موجود برای پیاده‌سازی این متدولوژی، خود به منبع جدیدی از پیچیدگی تبدیل شده‌اند.</p>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">معضلات رایج در اکوسیستم ابزارهای فعلی:</h4>

<div class="warning-box">
<strong>⚠️ چالش‌هایی که تیم‌ها هر روز با آن‌ها مواجه‌اند:</strong>
<ul style="padding-right: 20px; margin-top: 10px;">
<li style="margin-bottom: 8px;"><strong>وابستگی به اینترنت و سرور:</strong> بسیاری از ابزارها نیازمند اتصال دائمی به اینترنت و سرورهای ابری هستند. در محیط‌هایی با محدودیت شبکه یا الزامات امنیتی بالا، این وابستگی یک مانع جدی است.</li>
<li style="margin-bottom: 8px;"><strong>هزینه‌های اشتراک ماهانه:</strong> ابزارهای تجاری برای هر کاربر هزینه اشتراک دریافت می‌کنند. برای تیم‌های بزرگ، این هزینه به‌سرعت به رقم قابل‌توجهی تبدیل می‌شود.</li>
<li style="margin-bottom: 8px;"><strong>پیچیدگی بیش‌ازحد:</strong> ابزارهایی مانند Jira یا Azure DevOps صدها قابلیت دارند که اکثر تیم‌ها از کمتر از ۲۰ درصد آن‌ها استفاده می‌کنند. این پیچیدگی، منحنی یادگیری را بالا برده و بهره‌وری را کاهش می‌دهد.</li>
<li style="margin-bottom: 8px;"><strong>فرآیند نصب و راه‌اندازی سنگین:</strong> برخی ابزارها نیازمند نصب سرور، پیکربندی دیتابیس و مدیریت زیرساخت هستند که زمان و تخصص قابل‌توجهی می‌طلبد.</li>
<li style="margin-bottom: 8px;"><strong>قفل‌شدگی داده‌ها (Vendor Lock-in):</strong> داده‌های پروژه در سرورهای ارائه‌دهنده سرویس ذخیره می‌شوند و خروج از آن اکوسیستم، پرهزینه و دشوار است.</li>
<li style="margin-bottom: 8px;"><strong>عدم قابلیت حمل:</strong> انتقال پروژه از یک محیط به محیط دیگر (مثلاً از لپ‌تاپ شخصی به سیستم شرکت) در بسیاری از ابزارها فرآیندی پیچیده است.</li>
</ul>
</div>

<p>این معضلات، یک شکاف آشکار در بازار ابزارهای مدیریت پروژه ایجاد کرده بود: <strong>جای خالی ابزاری ساده، مستقل، رایگان و در عین حال حرفه‌ای</strong> که بدون هیچ پیش‌نیازی قابل استفاده باشد و در عین حال، قابلیت‌های پیشرفته‌ای مانند فیلتر چندبعدی، تاریخچه تغییرات، وابستگی بین کارت‌ها و یکپارچگی با سیستم‌های خارجی را ارائه دهد.</p>

<div class="highlight-box">
<strong>🎯 نقطه عطف:</strong> xKanban دقیقاً از همین شکاف متولد شد. هدف، خلق ابزاری بود که «صفر تا صد» مدیریت کانبان را بدون هیچ وابستگی خارجی، در یک فایل واحد و قابل حمل ارائه دهد — ابزاری که در کمتر از ۳ ثانیه باز شود و از همان لحظه اول قابل استفاده باشد.
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">نیازمندی‌های کلیدی که xKanban برای پاسخ به آن‌ها طراحی شد:</h4>
<table class="styled-table">
<thead>
<tr>
<th>ردیف</th>
<th>نیازمندی</th>
<th>وضعیت در ابزارهای موجود</th>
<th>پاسخ xKanban</th>
</tr>
</thead>
<tbody>
<tr>
<td>۱</td>
<td>اجرای آفلاین و بدون سرور</td>
<td style="color: var(--danger);">اکثراً ناممکن</td>
<td style="color: var(--success); font-weight: 600;">✓ کاملاً آفلاین</td>
</tr>
<tr>
<td>۲</td>
<td>بدون هزینه اشتراک</td>
<td style="color: var(--danger);">اشتراک ماهانه/سالانه</td>
<td style="color: var(--success); font-weight: 600;">✓ کاملاً رایگان</td>
</tr>
<tr>
<td>۳</td>
<td>راه‌اندازی فوری</td>
<td style="color: var(--warning);">نیاز به نصب و پیکربندی</td>
<td style="color: var(--success); font-weight: 600;">✓ یک کلیک</td>
</tr>
<tr>
<td>۴</td>
<td>قابلیت حمل داده‌ها</td>
<td style="color: var(--warning);">محدود و پیچیده</td>
<td style="color: var(--success); font-weight: 600;">✓ فایل JSON</td>
</tr>
<tr>
<td>۵</td>
<td>سادگی همراه با قدرت</td>
<td style="color: var(--danger);">یا ساده یا پیچیده</td>
<td style="color: var(--success); font-weight: 600;">✓ تعادل بهینه</td>
</tr>
<tr>
<td>۶</td>
<td>مالکیت کامل داده‌ها</td>
<td style="color: var(--danger);">ذخیره در سرور ثالث</td>
<td style="color: var(--success); font-weight: 600;">✓ کاملاً محلی</td>
</tr>
</tbody>
</table>

</div>
</div>

<!-- ═══════════════ بخش ۲: مزایای کلیدی ═══════════════ -->
<div class="section">
<div class="section-header">
<div class="section-number">۲</div>
<div class="section-title">
<h3>مزایای کلیدی محصول</h3>
<div class="subtitle">Key Product Advantages</div>
</div>
</div>
<div class="section-content">

<p>xKanban مجموعه‌ای از مزایای منحصربه‌فرد را ارائه می‌دهد که آن را از تمامی ابزارهای موجود در بازار متمایز می‌سازد. این مزایا نه صرفاً ویژگی‌های فنی، بلکه <strong>تصمیمات استراتژیک طراحی</strong> هستند که هر کدام پاسخی مستقیم به یکی از معضلات مطرح‌شده در بخش قبل می‌باشند.</p>

<div class="stats-grid">
<div class="stat-card">
<div class="stat-value">۰ ثانیه<br>نصب</div>
<div class="stat-label">بدون نیاز به هیچ فرآیند نصب یا پیکربندی</div>
</div>
<div class="stat-card">
<div class="stat-value">۱۰۰٪<br>آفلاین</div>
<div class="stat-label">اجرای کامل بدون نیاز به اینترنت یا سرور</div>
</div>
<div class="stat-card">
<div class="stat-value">۱ فایل<br>واحد</div>
<div class="stat-label">تمام محصول در یک فایل HTML مستقل</div>
</div>
</div>

<div class="feature-grid">
<div class="feature-card">
<div class="feature-icon">🚀</div>
<h5>راه‌اندازی آنی</h5>
<p>فایل HTML را در مرورگر باز کنید. تمام. هیچ سروری، هیچ دیتابیسی، هیچ اکانتی لازم نیست. در کمتر از ۳ ثانیه آماده استفاده هستید.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🔒</div>
<h5>حریم خصوصی مطلق</h5>
<p>تمام داده‌ها در مرورگر شما باقی می‌مانند. هیچ اطلاعاتی به هیچ سرور خارجی ارسال نمی‌شود. مالکیت داده‌ها ۱۰۰٪ در اختیار شماست.</p>
</div>
<div class="feature-card">
<div class="feature-icon">📦</div>
<h5>قابلیت حمل بی‌نظیر</h5>
<p>پروژه خود را به‌صورت فایل JSON ذخیره کنید و روی فلش، ایمیل یا هر فضای ابری منتقل کنید. در هر سیستمی قابل بارگذاری است.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🎨</div>
<h5>رابط کاربری مدرن و حرفه‌ای</h5>
<p>طراحی بصری جذاب با پشتیبانی از حالت تاریک/روشن، رنگ‌بندی سفارشی برای بردها، ستون‌ها و کارت‌ها و انیمیشن‌های روان.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🔍</div>
<h5>فیلتر چندبعدی پیشرفته</h5>
<p>فیلتر هم‌زمان بر اساس تگ، مسئول، وضعیت سررسید و جستجوی متنی. یافتن هر کارت در کمتر از یک ثانیه حتی در پروژه‌های بزرگ.</p>
</div>
<div class="feature-card">
<div class="feature-icon">📡</div>
<h5>یکپارچگی با Webhook</h5>
<p>ارسال خودکار رویدادهای پروژه به Slack، Teams یا هر سیستم خارجی. ساختار JSON استاندارد با eventId و timestamp.</p>
</div>
<div class="feature-card">
<div class="feature-icon">⌨️</div>
<h5>میانبرهای کیبورد حرفه‌ای</h5>
<p>بیش از ۸ میانبر کیبورد برای کاربران حرفه‌ای. از Ctrl+S برای ذخیره تا Alt+Arrow برای جابجایی سریع کارت‌ها بین ستون‌ها.</p>
</div>
<div class="feature-card">
<div class="feature-icon">📊</div>
<h5>آمار بلادرنگ پروژه</h5>
<p>مشاهده آنی تعداد کارت‌ها، درصد پیشرفت، کارت‌های عقب‌افتاده و نزدیک به موعد در نوار وضعیت پایین صفحه.</p>
</div>
</div>

<div class="success-box">
<strong>💎 مزیت رقابتی اصلی:</strong> xKanban تنها ابزاری است که ترکیب «سادگی یک فایل HTML» و «قدرت یک سیستم مدیریت پروژه حرفه‌ای» را به‌صورت هم‌زمان ارائه می‌دهد. هیچ ابزار دیگری در بازار این تعادل را برقرار نکرده است.
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">مقایسه با ابزارهای رایج بازار:</h4>
<div class="comparison-grid">
<div class="comparison-card">
<h4>ابزارهای تجاری (Jira, Trello, Asana)</h4>
<ul>
<li><span>هزینه</span> <span class="value" style="color: var(--danger);">اشتراک ماهانه بالا</span></li>
<li><span>نیاز به اینترنت</span> <span class="value" style="color: var(--danger);">الزامی</span></li>
<li><span>نصب و راه‌اندازی</span> <span class="value" style="color: var(--warning);">ثبت‌نام + پیکربندی</span></li>
<li><span>مالکیت داده</span> <span class="value" style="color: var(--danger);">سرور ثالث</span></li>
<li><span>پیچیدگی</span> <span class="value" style="color: var(--danger);">بسیار بالا</span></li>
<li><span>آفلاین</span> <span class="value" style="color: var(--danger);">ناممکن</span></li>
</ul>
</div>
<div class="comparison-card recommended">
<span class="card-badge">xKanban ✓</span>
<h4>xKanban</h4>
<ul>
<li><span>هزینه</span> <span class="value" style="color: var(--success);">کاملاً رایگان</span></li>
<li><span>نیاز به اینترنت</span> <span class="value" style="color: var(--success);">خیر</span></li>
<li><span>نصب و راه‌اندازی</span> <span class="value" style="color: var(--success);">یک کلیک</span></li>
<li><span>مالکیت داده</span> <span class="value" style="color: var(--success);">۱۰۰٪ محلی</span></li>
<li><span>پیچیدگی</span> <span class="value" style="color: var(--success);">بهینه و متعادل</span></li>
<li><span>آفلاین</span> <span class="value" style="color: var(--success);">کاملاً پشتیبانی</span></li>
</ul>
</div>
</div>

</div>
</div>

<!-- ═══════════════ بخش ۳: ارزش‌های سازمانی ═══════════════ -->
<div class="section">
<div class="section-header">
<div class="section-number">۳</div>
<div class="section-title">
<h3>ارزش‌های سازمانی خلق‌شده</h3>
<div class="subtitle">Organizational Value Proposition</div>
</div>
</div>
<div class="section-content">

<p>استفاده از xKanban در یک سازمان، فراتر از صرفاً «مدیریت کارها» است. این ابزار، ارزش‌های استراتژیک و عملیاتی ملموسی را در سطوح مختلف سازمان خلق می‌کند که در ادامه به تفصیل تشریح شده‌اند:</p>

<div class="value-card">
<div class="value-icon">💰</div>
<div class="value-body">
<h5>حذف کامل هزینه‌های لایسنس و اشتراک</h5>
<p>سازمان‌هایی که از ابزارهایی مانند Jira یا Azure DevOps استفاده می‌کنند، سالانه مبالغ قابل‌توجهی بابت لایسنس هر کاربر پرداخت می‌کنند. xKanban این هزینه را به صفر می‌رساند و بودجه آزادشده را برای سرمایه‌گذاری در حوزه‌های ارزشمندتر آزاد می‌کند.</p>
</div>
</div>

<div class="value-card">
<div class="value-icon">🔐</div>
<div class="value-body">
<h5>تطبیق با الزامات امنیتی و حریم خصوصی</h5>
<p>در سازمان‌هایی با حساسیت امنیتی بالا (نظامی، بانکی، بهداشتی)، ارسال داده‌های پروژه به سرورهای ابری خارجی ممنوع یا محدود است. xKanban با ذخیره‌سازی ۱۰۰٪ محلی، این الزامات را به‌صورت ذاتی رعایت می‌کند.</p>
</div>
</div>

<div class="value-card">
<div class="value-icon">⚡</div>
<div class="value-body">
<h5>کاهش زمان Onboarding اعضای جدید</h5>
<p>برخلاف ابزارهای پیچیده که آموزش آن‌ها هفته‌ها زمان می‌برد، xKanban به‌قدری ساده و شهودی است که یک عضو جدید تیم در کمتر از ۱۰ دقیقه با آن آشنا شده و شروع به کار می‌کند.</p>
</div>
</div>

<div class="value-card">
<div class="value-icon">📈</div>
<div class="value-body">
<h5>افزایش شفافیت و پاسخگویی در تیم</h5>
<p>با نمایش بصری وضعیت تمام کارها، تاریخچه جابجایی کارت‌ها و آمار بلادرنگ پیشرفت، هر عضو تیم و هر مدیری در هر لحظه دید کاملی از وضعیت پروژه دارد. این شفافیت، فرهنگ پاسخگویی را تقویت می‌کند.</p>
</div>
</div>

<div class="value-card">
<div class="value-icon">🔄</div>
<div class="value-body">
<h5>استانداردسازی فرآیندهای مدیریت پروژه</h5>
<p>xKanban تیم‌ها را به استفاده از یک متدولوژی استاندارد (کانبان) هدایت می‌کند. ستون‌های مشخص، جریان کار تعریف‌شده و تاریخچه تغییرات، همگی به ایجاد یک فرآیند تکرارپذیر و قابل اندازه‌گیری کمک می‌کنند.</p>
</div>
</div>

<div class="value-card">
<div class="value-icon">🛡️</div>
<div class="value-body">
<h5>حذف ریسک Vendor Lock-in</h5>
<p>داده‌های پروژه در فرمت استاندارد JSON ذخیره می‌شوند. این یعنی سازمان هیچ‌گاه در اکوسیستم یک ارائه‌دهنده خاص قفل نمی‌شود و در هر زمان می‌تواند داده‌های خود را به هر سیستم دیگری منتقل کند.</p>
</div>
</div>

<div class="value-card">
<div class="value-icon">🌐</div>
<div class="value-body">
<h5>یکپارچگی با اکوسیستم موجود سازمان</h5>
<p>از طریق مکانیزم Webhook، xKanban می‌تواند رویدادهای پروژه را به Slack، Microsoft Teams، سیستم‌های CI/CD یا هر API سفارشی ارسال کند. این یعنی xKanban به‌جای جایگزینی ابزارهای موجود، آن‌ها را تکمیل می‌کند.</p>
</div>
</div>

<div class="value-card">
<div class="value-icon">📉</div>
<div class="value-body">
<h5>کاهش سربار مدیریت زیرساخت</h5>
<p>بدون نیاز به سرور، دیتابیس، به‌روزرسانی‌های امنیتی و مانیتورینگ، تیم IT سازمان از بار مدیریت یک ابزار اضافی رها می‌شود. این صرفه‌جویی در زمان و منابع انسانی، ارزش پنهان اما بسیار قابل‌توجهی است.</p>
</div>
</div>

<div class="highlight-box">
<strong>📊 خلاصه ارزش‌آفرینی:</strong> xKanban در چهار بُعد اصلی برای سازمان ارزش خلق می‌کند: <strong>کاهش هزینه‌ها</strong> (حذف لایسنس و زیرساخت)، <strong>افزایش بهره‌وری</strong> (Onboarding سریع و رابط شهودی)، <strong>کاهش ریسک</strong> (امنیت داده و عدم وابستگی) و <strong>بهبود کیفیت فرآیندها</strong> (استانداردسازی و شفافیت).
</div>

</div>
</div>

<!-- ═══════════════ بخش ۴: معماری فنی ═══════════════ -->
<div class="section">
<div class="section-header">
<div class="section-number">۴</div>
<div class="section-title">
<h3>معماری فنی پروژه</h3>
<div class="subtitle">Technical Architecture & Code Structure</div>
</div>
</div>
<div class="section-content">

<p>xKanban به‌عنوان یک <strong>Single-Page Application (SPA)</strong> تک‌فایلی طراحی شده است که تمامی لایه‌های نمایش، منطق و داده را در یک فایل HTML واحد encapsulate می‌کند. این تصمیم معماری، اگرچه در نگاه اول غیرمعمول به نظر می‌رسد، اما با توجه به اهداف محصول (آفلاین بودن، قابلیت حمل و عدم وابستگی) منطقی‌ترین انتخاب ممکن بوده است.</p>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">الف) نمودار معماری لایه‌ای</h4>

<div class="architecture-diagram">
<div class="arch-layer">
<div class="arch-layer-title">🎨 Presentation Layer</div>
<div class="arch-components">
<span class="arch-chip">Toolbar UI</span>
<span class="arch-chip">Board Renderer</span>
<span class="arch-chip">Column Renderer</span>
<span class="arch-chip">Card Renderer</span>
<span class="arch-chip">Modal System</span>
<span class="arch-chip">Filter Panel</span>
<span class="arch-chip">Stats Bar</span>
<span class="arch-chip">Theme Engine</span>
</div>
</div>
<div class="arch-arrow">▼ Event Binding & State Observation ▼</div>
<div class="arch-layer">
<div class="arch-layer-title">⚙️ Application Logic Layer</div>
<div class="arch-components">
<span class="arch-chip gold">ProjectManager</span>
<span class="arch-chip gold">BoardManager</span>
<span class="arch-chip gold">ColumnManager</span>
<span class="arch-chip gold">CardManager</span>
<span class="arch-chip gold">TagManager</span>
<span class="arch-chip gold">FilterEngine</span>
<span class="arch-chip gold">WebhookService</span>
<span class="arch-chip gold">ShortcutHandler</span>
<span class="arch-chip gold">HistoryTracker</span>
</div>
</div>
<div class="arch-arrow">▼ Read / Write / Sync ▼</div>
<div class="arch-layer">
<div class="arch-layer-title">💾 Data & Persistence Layer</div>
<div class="arch-components">
<span class="arch-chip">AppState (In-Memory)</span>
<span class="arch-chip">LocalStorage Adapter</span>
<span class="arch-chip">AutoSave Engine</span>
<span class="arch-chip">JSON Serializer</span>
<span class="arch-chip">Export/Import Service</span>
<span class="arch-chip">Recovery Manager</span>
</div>
</div>
</div>

<h4 style="margin-top: 30px; color: var(--primary); font-size: 16px;">ب) مدل داده‌ای (Data Schema)</h4>
<p>ساختار داده‌ای پروژه از یک سلسله‌مراتب چهارسطحی پیروی می‌کند:</p>

<div class="code-box">
<span class="comment">// ساختار سلسله‌مراتبی داده‌ها</span><br>
<span class="key">Project</span><br>
&nbsp;&nbsp;├── <span class="key">id</span>: <span class="str">UUID</span><br>
&nbsp;&nbsp;├── <span class="key">title</span>: <span class="str">string</span><br>
&nbsp;&nbsp;├── <span class="key">description</span>: <span class="str">string</span><br>
&nbsp;&nbsp;├── <span class="key">createdAt</span>: <span class="str">ISO8601</span><br>
&nbsp;&nbsp;├── <span class="key">webhook</span>: { <span class="key">enabled</span>, <span class="key">url</span> }<br>
&nbsp;&nbsp;├── <span class="key">globalTags</span>: [<span class="key">Tag</span>]<br>
&nbsp;&nbsp;└── <span class="key">boards</span>: [<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="key">Board</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;├── <span class="key">id</span>, <span class="key">title</span>, <span class="key">backgroundColor</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;└── <span class="key">columns</span>: [<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="key">Column</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;├── <span class="key">id</span>, <span class="key">title</span>, <span class="key">order</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;└── <span class="key">cards</span>: [<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="key">Card</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;├── <span class="key">id</span>, <span class="key">title</span>, <span class="key">description</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;├── <span class="key">dueDate</span>, <span class="key">assignees</span>, <span class="key">tags</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;├── <span class="key">dependencies</span>, <span class="key">checklist</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;└── <span class="key">history</span>: [<span class="key">HistoryEntry</span>]<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;]<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;]<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;]<br>
</div>

<h4 style="margin-top: 30px; color: var(--primary); font-size: 16px;">ج) ماژول‌های اصلی و متدهای کلیدی</h4>
<p>در ادامه، مهم‌ترین ماژول‌های سیستم به همراه متدهای کلیدی هر کدام تشریح شده‌اند. برای حفظ خوانایی، بدنه متدها خلاصه شده و تنها امضای آن‌ها نمایش داده شده است:</p>

<h5 style="margin-top: 20px; color: var(--primary-light); font-size: 14px;">📁 ماژول ProjectManager</h5>
<ul class="method-list">
<li>
<span class="method-name">initProject()</span>
<span class="method-desc">مقداردهی اولیه پروژه</span>
</li>
<li>
<span class="method-name">loadProject(data)</span>
<span class="method-desc">بارگذاری پروژه از فایل JSON</span>
</li>
<li>
<span class="method-name">saveProject(filename)</span>
<span class="method-desc">ذخیره پروژه به‌صورت فایل JSON</span>
</li>
<li>
<span class="method-name">updateProjectSettings(title, desc, webhook)</span>
<span class="method-desc">به‌روزرسانی تنظیمات پروژه</span>
</li>
<li>
<span class="method-name">getProjectStats()</span>
<span class="method-desc">محاسبه آمار کلی پروژه</span>
</li>
</ul>

<h5 style="margin-top: 20px; color: var(--primary-light); font-size: 14px;">📊 ماژول BoardManager</h5>
<ul class="method-list">
<li>
<span class="method-name">createBoard(title, description, bgColor)</span>
<span class="method-desc">ایجاد برد جدید</span>
</li>
<li>
<span class="method-name">editBoard(boardId, data)</span>
<span class="method-desc">ویرایش برد موجود</span>
</li>
<li>
<span class="method-name">deleteBoard(boardId)</span>
<span class="method-desc">حذف برد با تأیید کاربر</span>
</li>
<li>
<span class="method-name">switchBoard(boardId)</span>
<span class="method-desc">تغییر برد فعال</span>
</li>
<li>
<span class="method-name">renderBoardList()</span>
<span class="method-desc">رندر لیست بردها در منو</span>
</li>
</ul>

<h5 style="margin-top: 20px; color: var(--primary-light); font-size: 14px;">📑 ماژول ColumnManager</h5>
<ul class="method-list">
<li>
<span class="method-name">addColumn(boardId, title, bgColor, textColor)</span>
<span class="method-desc">افزودن ستون جدید به برد</span>
</li>
<li>
<span class="method-name">editColumn(columnId, data)</span>
<span class="method-desc">ویرایش ویژگی‌های ستون</span>
</li>
<li>
<span class="method-name">deleteColumn(columnId)</span>
<span class="method-desc">حذف ستون و کارت‌های آن</span>
</li>
<li>
<span class="method-name">reorderColumns(draggedId, targetId)</span>
<span class="method-desc">تغییر ترتیب ستون‌ها با Drag & Drop</span>
</li>
<li>
<span class="method-name">renderColumns(boardId)</span>
<span class="method-desc">رندر ستون‌های برد فعال</span>
</li>
</ul>

<h5 style="margin-top: 20px; color: var(--primary-light); font-size: 14px;">📝 ماژول CardManager</h5>
<ul class="method-list">
<li>
<span class="method-name">createCard(columnId, cardData)</span>
<span class="method-desc">ایجاد کارت جدید در ستون</span>
</li>
<li>
<span class="method-name">updateCard(cardId, updates)</span>
<span class="method-desc">به‌روزرسانی اطلاعات کارت</span>
</li>
<li>
<span class="method-name">deleteCard(cardId)</span>
<span class="method-desc">حذف کارت از ستون</span>
</li>
<li>
<span class="method-name">moveCard(cardId, fromColumnId, toColumnId)</span>
<span class="method-desc">جابجایی کارت بین ستون‌ها</span>
</li>
<li>
<span class="method-name">addChecklistItem(cardId, text)</span>
<span class="method-desc">افزودن آیتم به چک‌لیست</span>
</li>
<li>
<span class="method-name">toggleChecklistItem(cardId, itemId)</span>
<span class="method-desc">تغییر وضعیت آیتم چک‌لیست</span>
</li>
<li>
<span class="method-name">addDependency(cardId, dependOnCardId)</span>
<span class="method-desc">ایجاد وابستگی بین کارت‌ها</span>
</li>
<li>
<span class="method-name">assignMember(cardId, memberName)</span>
<span class="method-desc">تخصیص مسئول به کارت</span>
</li>
<li>
<span class="method-name">getCardDueStatus(card)</span>
<span class="method-desc">تعیین وضعیت سررسید کارت</span>
</li>
<li>
<span class="method-name">openCardModal(cardId)</span>
<span class="method-desc">باز کردن پنجره ویرایش کارت</span>
</li>
</ul>

<h5 style="margin-top: 20px; color: var(--primary-light); font-size: 14px;">🏷️ ماژول TagManager</h5>
<ul class="method-list">
<li>
<span class="method-name">createTag(name, color)</span>
<span class="method-desc">ایجاد تگ عمومی جدید</span>
</li>
<li>
<span class="method-name">deleteTag(tagId)</span>
<span class="method-desc">حذف تگ و حذف از کارت‌ها</span>
</li>
<li>
<span class="method-name">assignTagToCard(cardId, tagId)</span>
<span class="method-desc">تخصیص تگ به کارت</span>
</li>
<li>
<span class="method-name">removeTagFromCard(cardId, tagId)</span>
<span class="method-desc">حذف تگ از کارت</span>
</li>
<li>
<span class="method-name">renderTagManager()</span>
<span class="method-desc">رندر پنل مدیریت تگ‌ها</span>
</li>
</ul>

<h5 style="margin-top: 20px; color: var(--primary-light); font-size: 14px;">🔍 ماژول FilterEngine</h5>
<ul class="method-list">
<li>
<span class="method-name">applyFilters(criteria)</span>
<span class="method-desc">اعمال فیلترهای چندبعدی</span>
</li>
<li>
<span class="method-name">filterByTags(selectedTags)</span>
<span class="method-desc">فیلتر بر اساس تگ‌ها</span>
</li>
<li>
<span class="method-name">filterByAssignees(selectedAssignees)</span>
<span class="method-desc">فیلتر بر اساس مسئولین</span>
</li>
<li>
<span class="method-name">filterByDueStatus(status)</span>
<span class="method-desc">فیلتر بر اساس وضعیت سررسید</span>
</li>
<li>
<span class="method-name">searchCards(query)</span>
<span class="method-desc">جستجوی متنی در کارت‌ها</span>
</li>
<li>
<span class="method-name">clearAllFilters()</span>
<span class="method-desc">پاک‌سازی تمام فیلترها</span>
</li>
</ul>

<h5 style="margin-top: 20px; color: var(--primary-light); font-size: 14px;">📡 ماژول WebhookService</h5>
<ul class="method-list">
<li>
<span class="method-name">sendEvent(eventType, payload)</span>
<span class="method-desc">ارسال رویداد به URL پیکربندی‌شده</span>
</li>
<li>
<span class="method-name">testWebhook(url)</span>
<span class="method-desc">تست اتصال Webhook</span>
</li>
<li>
<span class="method-name">buildEventPayload(eventType, data)</span>
<span class="method-desc">ساخت ساختار JSON استاندارد رویداد</span>
</li>
</ul>

<h5 style="margin-top: 20px; color: var(--primary-light); font-size: 14px;">💾 ماژول PersistenceEngine</h5>
<ul class="method-list">
<li>
<span class="method-name">autoSave()</span>
<span class="method-desc">ذخیره خودکار در LocalStorage</span>
</li>
<li>
<span class="method-name">loadAutoSave()</span>
<span class="method-desc">بارگذاری داده‌های ذخیره‌شده خودکار</span>
</li>
<li>
<span class="method-name">scheduleAutoSave()</span>
<span class="method-desc">زمان‌بندی ذخیره با Debounce</span>
</li>
<li>
<span class="method-name">exportToJSON()</span>
<span class="method-desc">خروجی فایل JSON</span>
</li>
<li>
<span class="method-name">importFromJSON(file)</span>
<span class="method-desc">بارگذاری از فایل JSON</span>
</li>
<li>
<span class="method-name">exportToMarkdown(boardId)</span>
<span class="method-desc">خروجی فرمت Markdown</span>
</li>
<li>
<span class="method-name">exportToCSV(boardId)</span>
<span class="method-desc">خروجی فرمت CSV</span>
</li>
<li>
<span class="method-name">checkRecovery()</span>
<span class="method-desc">بررسی وجود داده‌های بازیابی</span>
</li>
</ul>

<h5 style="margin-top: 20px; color: var(--primary-light); font-size: 14px;">⌨️ ماژول ShortcutHandler</h5>
<ul class="method-list">
<li>
<span class="method-name">registerShortcuts()</span>
<span class="method-desc">ثبت تمام میانبرهای کیبورد</span>
</li>
<li>
<span class="method-name">handleKeyDown(event)</span>
<span class="method-desc">پردازش رویدادهای کیبورد</span>
</li>
<li>
<span class="method-name">navigateCards(direction)</span>
<span class="method-desc">پیمایش بین کارت‌ها با Tab</span>
</li>
<li>
<span class="method-name">moveCardWithKeyboard(direction)</span>
<span class="method-desc">جابجایی کارت با Alt+Arrow</span>
</li>
</ul>

<h4 style="margin-top: 30px; color: var(--primary); font-size: 16px;">د) الگوهای طراحی به‌کاررفته</h4>
<table class="styled-table">
<thead>
<tr>
<th>الگو</th>
<th>محل استفاده</th>
<th>هدف</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>State-Driven Rendering</strong></td>
<td>کل رابط کاربری</td>
<td>رندر UI بر اساس وضعیت مرکزی AppState</td>
</tr>
<tr>
<td><strong>Observer / Pub-Sub</strong></td>
<td>ارتباط ماژول‌ها</td>
<td>اطلاع‌رسانی تغییرات وضعیت به ماژول‌های وابسته</td>
</tr>
<tr>
<td><strong>Debouncing</strong></td>
<td>AutoSave Engine</td>
<td>جلوگیری از نوشتن مکرر در LocalStorage</td>
</tr>
<tr>
<td><strong>Factory</strong></td>
<td>ایجاد موجودیت‌ها</td>
<td>تولید اشیاء Card، Column، Board با UUID</td>
</tr>
<tr>
<td><strong>Command</strong></td>
<td>Undo/Redo</td>
<td>ذخیره عملیات برای امکان بازگشت</td>
</tr>
<tr>
<td><strong>Singleton</strong></td>
<td>AppState</td>
<td>یک منبع واحد حقیقت برای کل برنامه</td>
</tr>
</tbody>
</table>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">ه) جریان داده (Data Flow)</h4>
<div class="info-box">
<strong>🔄 چرخه حیات یک عملیات کاربر:</strong><br><br>
<strong>۱. User Action:</strong> کاربر یک کارت را از ستون «در حال انجام» به ستون «تکمیل‌شده» می‌کشد.<br>
<strong>۲. Event Capture:</strong> رویداد Drag & Drop توسط <code>ColumnManager</code> دریافت می‌شود.<br>
<strong>۳. State Update:</strong> متد <code>moveCard()</code> از <code>CardManager</code> فراخوانی شده و AppState به‌روزرسانی می‌شود.<br>
<strong>۴. History Log:</strong> متد <code>HistoryTracker.log()</code> جابجایی را با timestamp ثبت می‌کند.<br>
<strong>۵. Re-Render:</strong> متد <code>renderColumns()</code> رابط کاربری را با وضعیت جدید به‌روزرسانی می‌کند.<br>
<strong>۶. AutoSave:</strong> متد <code>scheduleAutoSave()</code> با تأخیر ۱ ثانیه، وضعیت را در LocalStorage ذخیره می‌کند.<br>
<strong>۷. Webhook:</strong> در صورت فعال بودن، متد <code>sendEvent('card.moved', payload)</code> رویداد را ارسال می‌کند.<br>
<strong>۸. Stats Update:</strong> متد <code>getProjectStats()</code> آمار نوار وضعیت را به‌روزرسانی می‌کند.
</div>

</div>
</div>

<!-- ═══════════════ بخش ۵: مخاطبان هدف ═══════════════ -->
<div class="section">
<div class="section-header">
<div class="section-number">۵</div>
<div class="section-title">
<h3>مناسب برای چه سازمان‌ها و تیم‌هایی؟</h3>
<div class="subtitle">Target Audience & Ideal Use Cases</div>
</div>
</div>
<div class="section-content">

<p>xKanban به‌گونه‌ای طراحی شده که طیف وسیعی از سازمان‌ها و تیم‌ها بتوانند از آن بهره‌مند شوند. با این حال، برخی محیط‌ها بیشترین ارزش را از این ابزار دریافت خواهند کرد:</p>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">الف) سازمان‌های هدف</h4>

<div class="audience-grid">
<div class="audience-card">
<div class="audience-icon">🏢</div>
<h5>استارتاپ‌ها و شرکت‌های کوچک</h5>
<p>تیم‌هایی که بودجه محدود دارند و به ابزاری رایگان، سریع و بدون سربار نیاز دارند. xKanban جایگزین ایده‌آل Trello و Asana برای این تیم‌هاست.</p>
</div>
<div class="audience-card">
<div class="audience-icon">🏛️</div>
<h5>سازمان‌های دولتی و نظامی</h5>
<p>محیط‌هایی با محدودیت‌های امنیتی شدید که استفاده از سرویس‌های ابری خارجی ممنوع است. xKanban با ذخیره‌سازی ۱۰۰٪ محلی، این الزامات را رعایت می‌کند.</p>
</div>
<div class="audience-card">
<div class="audience-icon">🏥</div>
<h5>مراکز بهداشتی و درمانی</h5>
<p>بیمارستان‌ها و مراکز تحقیقاتی که داده‌های حساس بیماران و پروژه‌ها نباید از شبکه داخلی خارج شود.</p>
</div>
<div class="audience-card">
<div class="audience-icon">🏦</div>
<h5>مؤسسات مالی و بانکی</h5>
<p>سازمان‌هایی که تحت مقررات سخت‌گیرانه حفاظت از داده (مانند PCI-DSS) فعالیت می‌کنند و نمی‌توانند اطلاعات پروژه‌ها را در سرورهای ثالث ذخیره کنند.</p>
</div>
<div class="audience-card">
<div class="audience-icon">🎓</div>
<h5>دانشگاه‌ها و مراکز آموزشی</h5>
<p>اساتید و دانشجویانی که برای مدیریت پروژه‌های تحقیقاتی، پایان‌نامه‌ها و کارهای گروهی به ابزاری ساده و رایگان نیاز دارند.</p>
</div>
<div class="audience-card">
<div class="audience-icon">🌍</div>
<h5>تیم‌های دورکار و فریلنسرها</h5>
<p>افرادی که در محیط‌های مختلف کار می‌کنند و نیاز دارند پروژه خود را روی فلش یا فضای ابری شخصی حمل کنند.</p>
</div>
</div>

<h4 style="margin-top: 30px; color: var(--primary); font-size: 16px;">ب) تیم‌های هدف</h4>
<table class="styled-table">
<thead>
<tr>
<th>نوع تیم</th>
<th>نحوه استفاده از xKanban</th>
<th>ارزش اصلی</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>تیم‌های توسعه نرم‌افزار</strong></td>
<td>مدیریت Sprint، باگ‌ها و Featureها</td>
<td>یکپارچگی با CI/CD از طریق Webhook</td>
</tr>
<tr>
<td><strong>تیم‌های DevOps</strong></td>
<td>ردیابی Incidentها و Change Requestها</td>
<td>اجرای آفلاین در محیط‌های ایزوله</td>
</tr>
<tr>
<td><strong>تیم‌های بازاریابی</strong></td>
<td>مدیریت کمپین‌ها و محتوای تولیدی</td>
<td>سادگی و سرعت راه‌اندازی</td>
</tr>
<tr>
<td><strong>تیم‌های منابع انسانی</strong></td>
<td>ردیابی فرآیند استخدام و Onboarding</td>
<td>حریم خصوصی داده‌های پرسنلی</td>
</tr>
<tr>
<td><strong>تیم‌های طراحی</strong></td>
<td>مدیریت پروژه‌های طراحی و بازخورد</td>
<td>رابط بصری جذاب و رنگ‌بندی سفارشی</td>
</tr>
<tr>
<td><strong>تیم‌های پشتیبانی</strong></td>
<td>مدیریت تیکت‌ها و درخواست‌های مشتریان</td>
<td>آمار بلادرنگ و فیلتر پیشرفته</td>
</tr>
<tr>
<td><strong>مدیران محصول</strong></td>
<td>نقشه راه محصول و اولویت‌بندی ویژگی‌ها</td>
<td>وابستگی بین کارت‌ها و چک‌لیست</td>
</tr>
</tbody>
</table>

<div class="highlight-box">
<strong>🎯 قانون کلی:</strong> هر تیمی که با سؤال «الان هر کس دارد روی چه چیزی کار می‌کند و وضعیتش چیست؟» مواجه است، یک کاندیدای ایده‌آل برای استفاده از xKanban محسوب می‌شود.
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">ج) چه زمانی xKanban بهترین انتخاب نیست؟</h4>
<div class="info-box">
<strong>💡 شفافیت کامل:</strong> xKanban برای سناریوهای زیر طراحی نشده و در این موارد، ابزارهای تخصصی‌تر توصیه می‌شوند:
<ul style="padding-right: 20px; margin-top: 10px;">
<li style="margin-bottom: 8px;">تیم‌های بسیار بزرگ (بیش از ۵۰ نفر) که نیاز به مدیریت دسترسی سطحی و نقش‌های پیچیده دارند</li>
<li style="margin-bottom: 8px;">سازمان‌هایی که نیاز به هم‌زمانی بلادرنگ (Real-time Collaboration) بین چندین کاربر دارند</li>
<li style="margin-bottom: 8px;">محیط‌هایی که نیاز به یکپارچگی عمیق با اکوسیستم‌های خاص (مانند SAP یا Salesforce) دارند</li>
</ul>
</div>

</div>
</div>

<!-- ═══════════════ بخش ۶: جمع‌بندی ═══════════════ -->
<div class="section">
<div class="section-header">
<div class="section-number">۶</div>
<div class="section-title">
<h3>جمع‌بندی و گام‌های بعدی</h3>
<div class="subtitle">Summary & Next Steps</div>
</div>
</div>
<div class="section-content">

<p>xKanban پاسخی عملی و مهندسی‌شده به یکی از رایج‌ترین چالش‌های تیم‌های مدرن است: مدیریت بصری کارها بدون پیچیدگی، بدون هزینه و بدون وابستگی. این ابزار از دل تجربه واقعی در مدیریت پروژه‌های نرم‌افزاری متولد شده و هر ویژگی آن، پاسخی مستقیم به یک نیاز واقعی است.</p>

<div class="conclusion-box">
<h3>🎯 چرا xKanban؟</h3>
<p>زیرا مدیریت پروژه نباید خودش یک پروژه باشد.</p>
<p>xKanban با ترکیب سادگی، قدرت و استقلال، ابزاری را ارائه می‌دهد که از لحظه اول قابل استفاده است، داده‌های شما را در اختیار خودتان نگه می‌دارد و هیچ هزینه‌ای بر دوش سازمان نمی‌گذارد.</p>
<div class="highlight">
<strong>💎 ارزش نهایی:</strong><br>
صفر هزینه + صفر وابستگی + صفر پیچیدگی = ۱۰۰٪ بهره‌وری
</div>
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">گام‌های پیشنهادی برای شروع:</h4>
<table class="styled-table">
<thead>
<tr>
<th>گام</th>
<th>اقدام</th>
<th>زمان تخمینی</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>۱</strong></td>
<td>فایل xKanban.html را در مرورگر باز کنید</td>
<td>۳ ثانیه</td>
</tr>
<tr>
<td><strong>۲</strong></td>
<td>یک پروژه و برد آزمایشی ایجاد کنید</td>
<td>۲ دقیقه</td>
</tr>
<tr>
<td><strong>۳</strong></td>
<td>ستون‌ها و کارت‌های واقعی تیم خود را وارد کنید</td>
<td>۱۰ دقیقه</td>
</tr>
<tr>
<td><strong>۴</strong></td>
<td>تگ‌ها و مسئولین را تعریف کنید</td>
<td>۵ دقیقه</td>
</tr>
<tr>
<td><strong>۵</strong></td>
<td>پروژه را به‌صورت فایل JSON ذخیره کنید</td>
<td>۱ دقیقه</td>
</tr>
<tr class="highlight-row">
<td><strong>۶</strong></td>
<td>از xKanban لذت ببرید! 🎉</td>
<td>همیشگی</td>
</tr>
</tbody>
</table>

<div class="info-box">
<strong>📞 تماس و پشتیبانی:</strong> برای دریافت مشاوره فنی، سفارشی‌سازی xKanban بر اساس نیازهای خاص سازمانی یا یکپارچگی با سیستم‌های موجود، آماده برگزاری جلسه فنی هستیم. طراحی، تحلیل و توسعه توسط <strong>هادی خزاعی اصل</strong>، معمار و توسعه‌دهنده ارشد نرم‌افزار.
</div>

</div>
</div>

<div class="page-info">
این مستند جهت معرفی محصول و تحلیل فنی تهیه شده است. هرگونه استفاده تجاری از محتوای این مستند بدون اجازه کتبی ممنوع است.
</div>

</div>

<!-- ═══════════════ FOOTER ═══════════════ -->
<footer class="document-footer">
<div class="footer-brand">طراحی، تحلیل و توسعه: هادی خزاعی اصل</div>
<div style="font-size: 11px; opacity: 0.8; margin-bottom: 5px;">معمار و توسعه‌دهنده ارشد نرم‌افزار | متخصص .NET و Angular</div>
<div class="footer-divider"></div>
<div>تاریخ تهیه: یکشنبه ۲۲ شهریور ۱۴۰۵ (۱۳ سپتامبر ۲۰۲۶) &nbsp;|&nbsp; ساعت: ۱۰:۳۰ &nbsp;|&nbsp; نسخه: ۱.۰.۰ &nbsp;|&nbsp; طبقه‌بندی: عمومی</div>
</footer>

</div>
</body>
</html>

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Resources\Tools\Document\xKanban_Case_Study_2.html

<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>مستند جامع محصول xKanban | تحلیل مسئله، مزایا، معماری و مخاطبان هدف</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap');
:root {
--primary: #1a365d;
--primary-light: #2c5282;
--accent: #c9a961;
--accent-dark: #a8893f;
--bg: #faf9f6;
--card-bg: #ffffff;
--text: #1a202c;
--text-muted: #4a5568;
--border: #e2e8f0;
--success: #38a169;
--danger: #e53e3e;
--warning: #dd6b20;
--shadow: 0 4px 6px rgba(0,0,0,0.05), 0 10px 30px rgba(26,54,93,0.08);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Vazirmatn', Tahoma, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.9;
font-size: 15px;
padding: 30px 15px;
}
.document-container {
max-width: 950px;
margin: 0 auto;
background: var(--card-bg);
box-shadow: var(--shadow);
border-radius: 12px;
overflow: hidden;
}
.document-header {
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);
color: white;
padding: 50px 40px 40px;
position: relative;
border-bottom: 4px solid var(--accent);
}
.document-header::before {
content: '';
position: absolute;
top: 0; right: 0;
width: 200px; height: 200px;
background: radial-gradient(circle, rgba(201,169,97,0.15) 0%, transparent 70%);
border-radius: 50%;
transform: translate(50%, -50%);
}
.header-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
position: relative;
z-index: 1;
}
.company-badge {
display: flex;
align-items: center;
gap: 12px;
}
.company-logo {
width: 60px; height: 60px;
background: var(--accent);
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
.doc-meta {
text-align: left;
font-size: 12px;
opacity: 0.9;
}
.doc-meta div { margin-bottom: 3px; }
.document-title {
text-align: center;
position: relative;
z-index: 1;
padding: 20px 0;
}
.document-title .label {
display: inline-block;
background: rgba(201,169,97,0.2);
color: var(--accent);
padding: 4px 16px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
margin-bottom: 15px;
border: 1px solid rgba(201,169,97,0.4);
}
.document-title h1 {
font-size: 28px;
font-weight: 800;
margin-bottom: 10px;
letter-spacing: -0.5px;
}
.document-title h2 {
font-size: 16px;
font-weight: 400;
opacity: 0.9;
}
.document-body {
padding: 40px;
}
.intro-text {
background: linear-gradient(to left, #f7fafc, #edf2f7);
border-right: 4px solid var(--accent);
padding: 20px 25px;
border-radius: 8px;
margin-bottom: 35px;
font-size: 14px;
color: var(--text-muted);
}
.section {
margin-bottom: 40px;
page-break-inside: avoid;
}
.section-header {
display: flex;
align-items: center;
gap: 15px;
padding-bottom: 12px;
border-bottom: 2px solid var(--border);
margin-bottom: 20px;
}
.section-number {
width: 50px; height: 50px;
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 800;
font-size: 20px;
flex-shrink: 0;
box-shadow: 0 4px 10px rgba(26,54,93,0.2);
}
.section-title { flex: 1; }
.section-title h3 {
font-size: 19px;
font-weight: 700;
color: var(--primary);
margin-bottom: 2px;
}
.section-title .subtitle {
font-size: 12px;
color: var(--text-muted);
}
.section-content {
padding-right: 10px;
}
.section-content p {
margin-bottom: 12px;
text-align: justify;
text-indent: 20px;
}
.section-content p.no-indent { text-indent: 0; }
.highlight-box {
background: linear-gradient(to left, #fffaf0, #fef5e7);
border: 1px solid #f6e05e;
border-right: 4px solid var(--accent);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.highlight-box strong { color: var(--accent-dark); }
.info-box {
background: linear-gradient(to left, #ebf8ff, #e6f6ff);
border: 1px solid #90cdf4;
border-right: 4px solid var(--primary-light);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.success-box {
background: linear-gradient(to left, #f0fff4, #e6fffa);
border: 1px solid #9ae6b4;
border-right: 4px solid var(--success);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.warning-box {
background: linear-gradient(to left, #fffaf0, #fef5e7);
border: 1px solid #fbd38d;
border-right: 4px solid var(--warning);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.code-box {
background: #1a202c;
color: #e2e8f0;
border-right: 4px solid var(--accent);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 13px;
font-family: 'Consolas', 'Courier New', monospace;
direction: ltr;
text-align: left;
overflow-x: auto;
line-height: 1.7;
}
.code-box .key { color: #c9a961; }
.code-box .str { color: #68d391; }
.code-box .num { color: #63b3ed; }
.code-box .comment { color: #718096; font-style: italic; }
.styled-table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
}
.styled-table thead {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
}
.styled-table th {
padding: 14px 16px;
text-align: right;
font-weight: 600;
font-size: 13px;
}
.styled-table td {
padding: 13px 16px;
border-bottom: 1px solid var(--border);
font-size: 13px;
}
.styled-table tbody tr:last-child td { border-bottom: none; }
.styled-table tbody tr:nth-child(even) { background: #f8fafc; }
.styled-table .highlight-row {
background: #fffaf0 !important;
font-weight: 600;
}
.comparison-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin: 25px 0;
}
.comparison-card {
background: #f8fafc;
border: 2px solid var(--border);
border-radius: 10px;
padding: 20px;
position: relative;
}
.comparison-card.recommended {
border-color: var(--success);
background: linear-gradient(to left, #f0fff4, #e6fffa);
}
.comparison-card .card-badge {
position: absolute;
top: -10px;
right: 20px;
background: var(--success);
color: white;
padding: 3px 12px;
border-radius: 15px;
font-size: 11px;
font-weight: 600;
}
.comparison-card h4 {
font-size: 16px;
font-weight: 700;
color: var(--primary);
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 2px solid var(--border);
}
.comparison-card.recommended h4 { border-bottom-color: var(--success); }
.comparison-card ul { list-style: none; padding: 0; }
.comparison-card ul li {
padding: 8px 0;
border-bottom: 1px solid var(--border);
font-size: 13px;
display: flex;
justify-content: space-between;
}
.comparison-card ul li:last-child { border-bottom: none; }
.comparison-card ul li .value { font-weight: 600; color: var(--primary); }
.quote-box {
background: linear-gradient(135deg, #f7fafc, #edf2f7);
border-right: 4px solid var(--accent);
padding: 20px 25px;
border-radius: 8px;
margin: 20px 0;
position: relative;
}
.quote-box::before {
content: '"';
position: absolute;
top: -10px;
right: 15px;
font-size: 60px;
color: var(--accent);
opacity: 0.3;
font-family: Georgia, serif;
}
.quote-box p {
font-style: italic;
color: var(--text-muted);
margin-bottom: 10px;
text-indent: 0 !important;
}
.quote-box .source {
font-size: 12px;
color: var(--text-muted);
text-align: left;
font-style: normal;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
margin: 25px 0;
}
.stat-card {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
padding: 20px;
border-radius: 10px;
text-align: center;
box-shadow: 0 4px 12px rgba(26,54,93,0.2);
}
.stat-card .stat-value {
font-size: 24px;
font-weight: 800;
margin-bottom: 5px;
color: var(--accent);
line-height: 1.3;
}
.stat-card .stat-label {
font-size: 12px;
opacity: 0.9;
}
.feature-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
margin: 25px 0;
}
.feature-card {
background: #f8fafc;
border: 1px solid var(--border);
border-right: 4px solid var(--primary-light);
padding: 18px;
border-radius: 8px;
}
.feature-card .feature-icon {
font-size: 24px;
margin-bottom: 10px;
}
.feature-card h5 {
font-size: 15px;
font-weight: 700;
color: var(--primary);
margin-bottom: 8px;
}
.feature-card p {
font-size: 13px;
color: var(--text-muted);
text-indent: 0 !important;
margin-bottom: 0 !important;
}
.architecture-diagram {
background: linear-gradient(to left, #f7fafc, #edf2f7);
border: 2px solid var(--border);
border-radius: 10px;
padding: 25px;
margin: 25px 0;
text-align: center;
}
.arch-layer {
background: white;
border: 2px solid var(--primary-light);
border-radius: 8px;
padding: 15px;
margin: 10px auto;
max-width: 700px;
box-shadow: 0 2px 6px rgba(0,0,0,0.05);
}
.arch-layer-title {
font-weight: 700;
color: var(--primary);
margin-bottom: 10px;
font-size: 14px;
border-bottom: 2px solid var(--accent);
padding-bottom: 5px;
display: inline-block;
}
.arch-components {
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: center;
}
.arch-chip {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
padding: 6px 14px;
border-radius: 15px;
font-size: 12px;
font-weight: 500;
}
.arch-arrow {
text-align: center;
font-size: 24px;
color: var(--accent);
margin: 5px 0;
}
.conclusion-box {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
padding: 30px;
border-radius: 12px;
margin: 30px 0;
text-align: center;
box-shadow: 0 8px 20px rgba(26,54,93,0.3);
}
.conclusion-box h3 {
font-size: 22px;
font-weight: 800;
margin-bottom: 15px;
color: var(--accent);
}
.conclusion-box p {
font-size: 14px;
line-height: 1.8;
text-indent: 0 !important;
margin-bottom: 15px;
}
.conclusion-box .highlight {
background: rgba(201,169,97,0.2);
padding: 15px 20px;
border-radius: 8px;
margin-top: 20px;
border: 1px solid rgba(201,169,97,0.4);
}
.document-footer {
background: var(--primary);
color: white;
padding: 25px 40px;
text-align: center;
font-size: 12px;
margin-top: 40px;
}
.document-footer .footer-brand {
font-weight: 700;
color: var(--accent);
margin-bottom: 5px;
font-size: 14px;
}
.footer-divider {
width: 60px;
height: 2px;
background: var(--accent);
margin: 10px auto;
}
.page-info {
text-align: center;
padding: 15px;
font-size: 11px;
color: var(--text-muted);
border-top: 1px solid var(--border);
margin-top: 30px;
}
@media print {
body { background: white; padding: 0; }
.document-container { box-shadow: none; border-radius: 0; }
.section { page-break-inside: avoid; }
}
@media (max-width: 768px) {
.document-body { padding: 25px 20px; }
.document-header { padding: 30px 20px; }
.comparison-grid { grid-template-columns: 1fr; }
.stats-grid { grid-template-columns: 1fr; }
.feature-grid { grid-template-columns: 1fr; }
.header-top { flex-direction: column; gap: 15px; }
.doc-meta { text-align: center; }
}
</style>
</head>
<body>
<div class="document-container">
<header class="document-header">
<div class="header-top">
<div class="company-badge">
<div class="company-logo"></div>
</div>
<div class="doc-meta">
<div>شماره مستند: PROD-XKN-1405-002</div>
<div>نسخه: ۱.۰.۰</div>
<div>تاریخ تهیه: یکشنبه ۲۲ شهریور ۱۴۰۵ (۱۳ سپتامبر ۲۰۲۶)</div>
<div>ساعت: ۱۱:۳۰</div>
<div>طبقه‌بندی: داخلی - محصولی</div>
</div>
</div>
<div class="document-title">
<span class="label">PRODUCT DOCUMENTATION</span>
<h1>مستند جامع محصول xKanban</h1>
<h2>تحلیل مسئله، مزایا، ارزش‌های سازمانی، معماری فنی و مخاطبان هدف</h2>
</div>
</header>

<div class="document-body">
<div class="intro-text">
<strong>تهیه‌کننده:</strong> هادی خزاعی اصل | معمار و توسعه‌دهنده ارشد نرم‌افزار<br>
<strong>مخاطبان مستند:</strong> مدیران فنی، تصمیم‌گیرندگان سازمانی، رهبران تیم و معماران نرم‌افزار<br>
این مستند با هدف ارائه تصویری جامع از چرایی خلق محصول <strong>xKanban</strong>، مزایای رقابتی آن، ارزش‌های قابل‌خلق در سازمان، معماری فنی دقیق و شناسایی مخاطبان هدف تهیه شده است.
</div>

<!-- ═══════════════════════════════════════════════════════ -->
<!-- بخش ۱: شرح مسئله -->
<!-- ═══════════════════════════════════════════════════════ -->
<div class="section">
<div class="section-header">
<div class="section-number">۱</div>
<div class="section-title">
<h3>شرح مسئله و ضرورت خلق ابزار</h3>
<div class="subtitle">Problem Statement & Motivation</div>
</div>
</div>
<div class="section-content">
<p>در دنیای توسعه نرم‌افزار و مدیریت پروژه، تیم‌ها با چالش‌های مشترکی در زمینه پیگیری وظایف، شفافیت وضعیت کارها و هماهنگی بین اعضا مواجه هستند. این چالش‌ها مختص سازمان خاصی نیستند، بلکه الگویی تکرارشونده در سراسر صنعت محسوب می‌شوند.</p>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">معضلات رایج در مدیریت وظایف:</h4>

<div class="warning-box">
<strong>⚠️ وابستگی اجباری به زیرساخت سرور:</strong>
<ul style="padding-right: 20px; margin-top: 10px;">
<li style="margin-bottom: 8px;">بخش عمده‌ای از ابزارهای مدیریت پروژه، نیازمند راه‌اندازی سرور، پایگاه داده، پیکربندی شبکه و نگهداری مداوم هستند</li>
<li style="margin-bottom: 8px;">تیم‌های کوچک و فریلنسرها اغلب فاقد زیرساخت و دانش فنی لازم برای استقرار این ابزارها هستند</li>
<li style="margin-bottom: 8px;">هزینه‌های سرور و نگهداری، بار مالی اضافی بر پروژه‌های کوچک تحمیل می‌کند</li>
</ul>
</div>

<div class="warning-box">
<strong>⚠️ قفل‌شدگی در اکوسیستم ابری:</strong>
<ul style="padding-right: 20px; margin-top: 10px;">
<li style="margin-bottom: 8px;">ابزارهای SaaS، داده‌های سازمان را روی سرورهای شخص ثالث ذخیره می‌کنند که نگرانی‌های امنیتی و حریم خصوصی ایجاد می‌کند</li>
<li style="margin-bottom: 8px;">وابستگی به اتصال اینترنت پایدار، کار در محیط‌های آفلاین یا با پهنای باند محدود را غیرممکن می‌سازد</li>
<li style="margin-bottom: 8px;">تغییر قیمت‌گذاری یا تعطیلی سرویس‌دهنده، ریسک از دست رفتن داده‌ها را به همراه دارد</li>
</ul>
</div>

<div class="warning-box">
<strong>⚠️ پیچیدگی بیش از حد ابزارهای موجود:</strong>
<ul style="padding-right: 20px; margin-top: 10px;">
<li style="margin-bottom: 8px;">بسیاری از ابزارها با انبوهی از ویژگی‌های غیرضروری، منحنی یادگیری بالایی دارند</li>
<li style="margin-bottom: 8px;">تیم‌ها زمان قابل‌توجهی را صرف یادگیری ابزار می‌کنند به جای تمرکز بر کار اصلی</li>
<li style="margin-bottom: 8px;">سفارشی‌سازی فرآیندها اغلب نیازمند پیکربندی‌های پیچیده و دانش تخصصی است</li>
</ul>
</div>

<div class="warning-box">
<strong>⚠️ عدم قابلیت حمل و انتقال داده‌ها:</strong>
<ul style="padding-right: 20px; margin-top: 10px;">
<li style="margin-bottom: 8px;">خروجی گرفتن از داده‌ها و انتقال آن‌ها بین ابزارها، فرآیندی دشوار و زمان‌بر است</li>
<li style="margin-bottom: 8px;">فرمت‌های اختصاصی، امکان استفاده آفلاین یا اشتراک‌گذاری ساده پروژه را محدود می‌کنند</li>
<li style="margin-bottom: 8px;">عدم امکان آرشیو کردن پروژه به صورت فایل مستقل، مدیریت بلندمدت را مختل می‌کند</li>
</ul>
</div>

<div class="highlight-box">
<strong>🎯 نتیجه‌گیری از تحلیل مسئله:</strong> نیاز به ابزاری احساس می‌شد که بدون نیاز به سرور، بدون وابستگی به اینترنت، بدون پیچیدگی غیرضروری و با قابلیت حمل کامل، امکان مدیریت حرفه‌ای پروژه‌ها را بر اساس متدولوژی کانبان فراهم آورد. <strong>xKanban</strong> پاسخی مستقیم به این نیاز است.
</div>
</div>
</div>

<!-- ═══════════════════════════════════════════════════════ -->
<!-- بخش ۲: مزایای کلیدی محصول -->
<!-- ═══════════════════════════════════════════════════════ -->
<div class="section">
<div class="section-header">
<div class="section-number">۲</div>
<div class="section-title">
<h3>مزایای کلیدی محصول</h3>
<div class="subtitle">Key Product Advantages</div>
</div>
</div>
<div class="section-content">
<p>xKanban با رویکردی متفاوت از ابزارهای رایج بازار طراحی شده و مزایایی را ارائه می‌دهد که هر یک به تنهایی می‌تواند دلیلی قانع‌کننده برای انتخاب این ابزار باشد:</p>

<div class="stats-grid">
<div class="stat-card">
<div class="stat-value">صفر<br>سرور</div>
<div class="stat-label">بدون نیاز به هیچ زیرساخت سروری</div>
</div>
<div class="stat-card">
<div class="stat-value">یک<br>فایل</div>
<div class="stat-label">توزیع و اجرا با یک فایل HTML</div>
</div>
<div class="stat-card">
<div class="stat-value">۱۰۰٪<br>آفلاین</div>
<div class="stat-label">کارکرد کامل بدون اتصال اینترنت</div>
</div>
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">مزیت ۱: استقلال مطلق از زیرساخت</h4>
<p class="no-indent">xKanban به صورت کامل در مرورگر کاربر اجرا می‌شود. هیچ سروری نیاز نیست، هیچ پایگاه داده‌ای پیکربندی نمی‌شود، هیچ وابستگی به سرویس خارجی وجود ندارد. کاربر فقط یک فایل HTML را باز می‌کند و بلافاصله شروع به کار می‌کند. این یعنی <strong>زمان راه‌اندازی: صفر ثانیه</strong>.</p>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">مزیت ۲: مالکیت کامل داده‌ها</h4>
<p class="no-indent">تمامی داده‌ها در مرورگر محلی کاربر و در فایل‌های JSON خروجی ذخیره می‌شوند. هیچ بایتی از اطلاعات پروژه به سرور خارجی ارسال نمی‌شود. این ویژگی برای سازمان‌هایی که با داده‌های حساس کار می‌کنند، یک مزیت حیاتی و غیرقابل‌مذاکره است.</p>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">مزیت ۳: قابلیت حمل بی‌نظیر</h4>
<p class="no-indent">کل پروژه شامل بردها، ستون‌ها، کارت‌ها، تگ‌ها و تنظیمات در یک فایل JSON قابل‌صدور است. این فایل را می‌توان از طریق ایمیل ارسال کرد، روی فلش ذخیره کرد، در فضای ابری شخصی نگه داشت یا حتی پرینت گرفت. پروژه شما، همیشه در جیب شماست.</p>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">مزیت ۴: غنای عملکردی در عین سادگی</h4>
<p class="no-indent">برخلاف تصور رایج که ابزارهای ساده فاقد قابلیت‌های پیشرفته هستند، xKanban مجموعه‌ای غنی از ویژگی‌ها را ارائه می‌دهد:</p>

<div class="feature-grid">
<div class="feature-card">
<div class="feature-icon">📊</div>
<h5>بردهای چندگانه</h5>
<p>مدیریت همزمان چندین برد مستقل در یک پروژه با جابجایی آنی</p>
</div>
<div class="feature-card">
<div class="feature-icon">🎨</div>
<h5>شخصی‌سازی بصری کامل</h5>
<p>رنگ‌بندی مستقل برای بردها، ستون‌ها و کارت‌ها جهت شناسایی سریع</p>
</div>
<div class="feature-card">
<div class="feature-icon">👥</div>
<h5>تخصیص چندمسئوله</h5>
<p>امکان اختصاص چندین نفر به هر کارت برای کارهای تیمی</p>
</div>
<div class="feature-card">
<div class="feature-icon">🔗</div>
<h5>مدیریت وابستگی‌ها</h5>
<p>تعریف روابط پیش‌نیازی بین کارت‌ها برای درک زنجیره کار</p>
</div>
<div class="feature-card">
<div class="feature-icon">✅</div>
<h5>چک‌لیست ریزوظایف</h5>
<p>شکستن کارت‌ها به وظایف کوچکتر با قابلیت پیگیری پیشرفت</p>
</div>
<div class="feature-card">
<div class="feature-icon">📜</div>
<h5>تاریخچه جابجایی</h5>
<p>ثبت خودکار تمام جابجایی‌های کارت بین ستون‌ها با زمان دقیق</p>
</div>
<div class="feature-card">
<div class="feature-icon">📡</div>
<h5>یکپارچگی Webhook</h5>
<p>ارسال رویدادها به سیستم‌های خارجی با ساختار استاندارد JSON</p>
</div>
<div class="feature-card">
<div class="feature-icon">⌨️</div>
<h5>میانبرهای کیبورد</h5>
<p>پشتیبانی کامل از کیبورد برای کاربران حرفه‌ای و افزایش سرعت</p>
</div>
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">مزیت ۵: خروجی چندفرمتی</h4>
<p class="no-indent">xKanban علاوه بر فرمت JSON برای پشتیبان‌گیری کامل، امکان خروجی گرفتن به فرمت‌های <strong>Markdown</strong> و <strong>CSV</strong> را نیز فراهم می‌آورد. این یعنی داده‌های شما هرگز در یک فرمت اختصاصی قفل نمی‌شوند و همیشه قابل استفاده در ابزارهای دیگر هستند.</p>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">مزیت ۶: بازیابی خودکار هوشمند</h4>
<p class="no-indent">سیستم AutoSave به صورت خودکار تغییرات را در مرورگر ذخیره می‌کند. در صورت بسته شدن ناگهانی مرورگر یا قطع برق، در جلسه بعدی سیستم به صورت خودکار داده‌های ذخیره‌شده را شناسایی کرده و گزینه بازیابی را ارائه می‌دهد.</p>

<div class="success-box">
<strong>✨ مزیت رقابتی نهایی:</strong> xKanban تنها ابزاری است که همزمان تمامی ویژگی‌های «بدون سرور»، «آفلاین کامل»، «مالکیت داده»، «قابلیت حمل»، «غنای عملکردی» و «خروجی چندفرمتی» را در یک بسته واحد ارائه می‌دهد. هیچ ابزار دیگری در بازار این ترکیب منحصربه‌فرد را فراهم نمی‌آورد.
</div>
</div>
</div>

<!-- ═══════════════════════════════════════════════════════ -->
<!-- بخش ۳: ارزش‌های سازمانی -->
<!-- ═══════════════════════════════════════════════════════ -->
<div class="section">
<div class="section-header">
<div class="section-number">۳</div>
<div class="section-title">
<h3>ارزش‌های قابل‌خلق در سازمان</h3>
<div class="subtitle">Organizational Value Proposition</div>
</div>
</div>
<div class="section-content">
<p>استفاده از xKanban فراتر از یک ابزار مدیریت وظایف ساده است. این محصول می‌تواند ارزش‌های استراتژیک و عملیاتی ملموسی را در سازمان ایجاد کند:</p>

<table class="styled-table">
<thead>
<tr>
<th>حوزه ارزش</th>
<th>ارزش ایجادشده</th>
<th>تأثیر عملی</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>💰 کاهش هزینه‌ها</strong></td>
<td>حذف هزینه لایسنس ابزارهای SaaS، سرور، پایگاه داده و نگهداری</td>
<td>صرفه‌جویی سالانه قابل‌توجه در بودجه فناوری اطلاعات</td>
</tr>
<tr>
<td><strong>⚡ افزایش سرعت</strong></td>
<td>راه‌اندازی آنی بدون فرآیند خرید، نصب و پیکربندی</td>
<td>تیم‌ها در کمتر از یک دقیقه شروع به کار می‌کنند</td>
</tr>
<tr>
<td><strong>🔒 امنیت داده‌ها</strong></td>
<td>عدم خروج داده‌ها از محیط محلی سازمان</td>
<td>انطباق با الزامات حریم خصوصی و مقررات حفاظت از داده</td>
</tr>
<tr>
<td><strong>🔄 تداوم کسب‌وکار</strong></td>
<td>کارکرد مستقل از اینترنت و سرویس‌های خارجی</td>
<td>عدم توقف کار تیم در صورت قطعی اینترنت یا اختلال سرویس</td>
</tr>
<tr>
<td><strong>📈 شفافیت فرآیندها</strong></td>
<td>دید بلادرنگ به وضعیت تمام کارها و گلوگاه‌ها</td>
<td>تصمیم‌گیری مبتنی بر داده و شناسایی سریع موانع</td>
</tr>
<tr>
<td><strong>🤝 بهبود همکاری تیمی</strong></td>
<td>اشتراک‌گذاری آسان پروژه از طریق فایل JSON</td>
<td>هماهنگی بهتر بین اعضا حتی در محیط‌های توزیع‌شده</td>
</tr>
<tr>
<td><strong>📚 انباشت دانش</strong></td>
<td>تاریخچه کامل جابجایی‌ها و آرشیو پروژه‌ها</td>
<td>امکان بازنگری فرآیندها و یادگیری از تجربیات گذشته</td>
</tr>
<tr>
<td><strong>🎯 استانداردسازی</strong></td>
<td>اجرای دقیق متدولوژی کانبان با ساختار یکپارچه</td>
<td>ایجاد زبان مشترک و فرآیند یکنواخت در تمام تیم‌ها</td>
</tr>
<tr>
<td><strong>🔌 یکپارچگی سیستمی</strong></td>
<td>ارتباط با ابزارهای خارجی از طریق Webhook</td>
<td>خودکارسازی اعلان‌ها و اتصال به اکوسیستم موجود سازمان</td>
</tr>
<tr>
<td><strong>🧠 کاهش بار شناختی</strong></td>
<td>رابط کاربری ساده و متمرکز بدون ویژگی‌های اضافی</td>
<td>کاهش زمان آموزش و افزایش تمرکز تیم بر کار اصلی</td>
</tr>
</tbody>
</table>

<div class="quote-box">
<p>ابزاری که تیم واقعاً از آن استفاده کند، ارزشمندتر از ابزاری است که فقط خریداری شده ولی کسی سراغش نمی‌رود. سادگی xKanban تضمین می‌کند که نرخ پذیرش در تیم به حداکثر می‌رسد.</p>
<div class="source">— اصل پذیرش فناوری در مهندسی نرم‌افزار</div>
</div>

<div class="highlight-box">
<strong>📊 ارزش تجمعی:</strong> هر یک از ارزش‌های فوق به تنهایی مفید است، اما ترکیب آن‌ها یک اثر هم‌افزایی (Synergy) ایجاد می‌کند: تیمی که سریع شروع می‌کند، امن کار می‌کند، شفافیت دارد و داده‌هایش قابل حمل است، به مراتب چابک‌تر و تاب‌آورتر از تیمی است که هر یک از این مزایا را از ابزارهای جداگانه تأمین می‌کند.
</div>
</div>
</div>

<!-- ═══════════════════════════════════════════════════════ -->
<!-- بخش ۴: معماری دقیق پروژه -->
<!-- ═══════════════════════════════════════════════════════ -->
<div class="section">
<div class="section-header">
<div class="section-number">۴</div>
<div class="section-title">
<h3>معماری فنی پروژه</h3>
<div class="subtitle">Technical Architecture Deep Dive</div>
</div>
</div>
<div class="section-content">
<p>xKanban بر پایه معماری <strong>Single-Page Application (SPA)</strong> با الگوی <strong>State-Driven Rendering</strong> پیاده‌سازی شده است. تمامی منطق در سمت کلاینت اجرا می‌شود و از معماری لایه‌ای سه‌تایی Presentation / Application / Data استفاده شده است.</p>

<div class="architecture-diagram">
<div class="arch-layer">
<div class="arch-layer-title">🎨 Presentation Layer</div>
<div class="arch-components">
<span class="arch-chip">Toolbar Renderer</span>
<span class="arch-chip">Board View</span>
<span class="arch-chip">Column Renderer</span>
<span class="arch-chip">Card Renderer</span>
<span class="arch-chip">Modal System</span>
<span class="arch-chip">Filter Panel</span>
<span class="arch-chip">Toast Notifications</span>
</div>
</div>
<div class="arch-arrow">▼</div>
<div class="arch-layer">
<div class="arch-layer-title">⚙️ Application Layer</div>
<div class="arch-components">
<span class="arch-chip">Project Manager</span>
<span class="arch-chip">Board Manager</span>
<span class="arch-chip">Column Manager</span>
<span class="arch-chip">Card Manager</span>
<span class="arch-chip">Tag Manager</span>
<span class="arch-chip">Filter Engine</span>
<span class="arch-chip">Webhook Service</span>
<span class="arch-chip">Shortcut Handler</span>
<span class="arch-chip">Export Engine</span>
</div>
</div>
<div class="arch-arrow">▼</div>
<div class="arch-layer">
<div class="arch-layer-title">💾 Data Layer</div>
<div class="arch-components">
<span class="arch-chip">State Container</span>
<span class="arch-chip">LocalStorage Adapter</span>
<span class="arch-chip">AutoSave Engine</span>
<span class="arch-chip">JSON Serializer</span>
<span class="arch-chip">UUID Generator</span>
</div>
</div>
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">الف) متدهای راه‌اندازی و چرخه حیات</h4>
<div class="code-box">
<span class="comment">// ── راه‌اندازی اولیه برنامه ──</span><br>
<span class="key">function</span> <span class="key">init</span>() { ... }<br><br>
<span class="comment">// ── رندر کامل رابط کاربری ──</span><br>
<span class="key">function</span> <span class="key">render</span>() { ... }<br><br>
<span class="comment">// ── رندر نوار ابزار بالا ──</span><br>
<span class="key">function</span> <span class="key">renderToolbar</span>() { ... }<br><br>
<span class="comment">// ── رندر برد فعال ──</span><br>
<span class="key">function</span> <span class="key">renderBoard</span>() { ... }
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">ب) متدهای مدیریت پروژه و پایداری</h4>
<div class="code-box">
<span class="comment">// ── ذخیره پروژه در LocalStorage ──</span><br>
<span class="key">function</span> <span class="key">saveProject</span>() { ... }<br><br>
<span class="comment">// ── بارگذاری پروژه از LocalStorage ──</span><br>
<span class="key">function</span> <span class="key">loadProject</span>() { ... }<br><br>
<span class="comment">// ── ذخیره خودکار با Debounce ──</span><br>
<span class="key">function</span> <span class="key">scheduleAutoSave</span>() { ... }<br><br>
<span class="comment">// ── بازیابی داده‌های ذخیره خودکار ──</span><br>
<span class="key">function</span> <span class="key">recoverAutoSave</span>() { ... }<br><br>
<span class="comment">// ── تنظیمات پروژه (عنوان، توضیحات، وب‌هوک) ──</span><br>
<span class="key">function</span> <span class="key">openProjectSettings</span>() { ... }<br>
<span class="key">function</span> <span class="key">saveProjectSettings</span>() { ... }
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">ج) متدهای مدیریت بردها</h4>
<div class="code-box">
<span class="comment">// ── ایجاد برد جدید ──</span><br>
<span class="key">function</span> <span class="key">createBoard</span>() { ... }<br><br>
<span class="comment">// ── ویرایش برد موجود ──</span><br>
<span class="key">function</span> <span class="key">editBoard</span>(boardId) { ... }<br><br>
<span class="comment">// ── حذف برد ──</span><br>
<span class="key">function</span> <span class="key">deleteBoard</span>(boardId) { ... }<br><br>
<span class="comment">// ── جابجایی بین بردها ──</span><br>
<span class="key">function</span> <span class="key">switchBoard</span>(boardId) { ... }
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">د) متدهای مدیریت ستون‌ها</h4>
<div class="code-box">
<span class="comment">// ── ایجاد ستون جدید ──</span><br>
<span class="key">function</span> <span class="key">createColumn</span>() { ... }<br><br>
<span class="comment">// ── ویرایش ستون ──</span><br>
<span class="key">function</span> <span class="key">editColumn</span>(columnId) { ... }<br><br>
<span class="comment">// ── حذف ستون ──</span><br>
<span class="key">function</span> <span class="key">deleteColumn</span>(columnId) { ... }<br><br>
<span class="comment">// ── جابجایی ستون با Drag & Drop ──</span><br>
<span class="key">function</span> <span class="key">moveColumn</span>(fromIndex, toIndex) { ... }
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">هـ) متدهای مدیریت کارت‌ها (غنی‌ترین ماژول)</h4>
<div class="code-box">
<span class="comment">// ── ایجاد کارت جدید ──</span><br>
<span class="key">function</span> <span class="key">createCard</span>(columnId) { ... }<br><br>
<span class="comment">// ── ویرایش کارت (Modal کامل) ──</span><br>
<span class="key">function</span> <span class="key">editCard</span>(cardId) { ... }<br><br>
<span class="comment">// ── حذف کارت ──</span><br>
<span class="key">function</span> <span class="key">deleteCard</span>(cardId) { ... }<br><br>
<span class="comment">// ── جابجایی کارت بین ستون‌ها ──</span><br>
<span class="key">function</span> <span class="key">moveCard</span>(cardId, targetColumnId) { ... }<br><br>
<span class="comment">// ── افزودن/حذف مسئول ──</span><br>
<span class="key">function</span> <span class="key">addAssignee</span>(cardId, name) { ... }<br>
<span class="key">function</span> <span class="key">removeAssignee</span>(cardId, name) { ... }<br><br>
<span class="comment">// ── مدیریت چک‌لیست ──</span><br>
<span class="key">function</span> <span class="key">addChecklistItem</span>(cardId, text) { ... }<br>
<span class="key">function</span> <span class="key">toggleChecklistItem</span>(cardId, itemIndex) { ... }<br>
<span class="key">function</span> <span class="key">removeChecklistItem</span>(cardId, itemIndex) { ... }<br><br>
<span class="comment">// ── مدیریت وابستگی‌ها ──</span><br>
<span class="key">function</span> <span class="key">addDependency</span>(cardId, dependencyId) { ... }<br>
<span class="key">function</span> <span class="key">removeDependency</span>(cardId, dependencyId) { ... }<br><br>
<span class="comment">// ── ثبت تاریخچه جابجایی ──</span><br>
<span class="key">function</span> <span class="key">addHistoryEntry</span>(cardId, fromColumn, toColumn) { ... }
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">و) متدهای مدیریت تگ‌ها</h4>
<div class="code-box">
<span class="comment">// ── مدیریت تگ‌های عمومی پروژه ──</span><br>
<span class="key">function</span> <span class="key">openTagManager</span>() { ... }<br>
<span class="key">function</span> <span class="key">addTag</span>(name, color) { ... }<br>
<span class="key">function</span> <span class="key">removeTag</span>(tagId) { ... }<br><br>
<span class="comment">// ── تخصیص/حذف تگ به کارت ──</span><br>
<span class="key">function</span> <span class="key">assignTagToCard</span>(cardId, tagId) { ... }<br>
<span class="key">function</span> <span class="key">removeTagFromCard</span>(cardId, tagId) { ... }
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">ز) متدهای فیلتر و جستجو</h4>
<div class="code-box">
<span class="comment">// ── فیلتر چندبعدی کارت‌ها ──</span><br>
<span class="key">function</span> <span class="key">filterCards</span>() { ... }<br><br>
<span class="comment">// ── جستجوی متنی در کارت‌ها ──</span><br>
<span class="key">function</span> <span class="key">searchCards</span>(query) { ... }<br><br>
<span class="comment">// ── پاک‌سازی فیلترها ──</span><br>
<span class="key">function</span> <span class="key">clearFilters</span>() { ... }
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">ح) متدهای صادرات و واردات</h4>
<div class="code-box">
<span class="comment">// ── صادرات پروژه به فایل JSON ──</span><br>
<span class="key">function</span> <span class="key">exportJSON</span>() { ... }<br><br>
<span class="comment">// ── واردات پروژه از فایل JSON ──</span><br>
<span class="key">function</span> <span class="key">importJSON</span>(file) { ... }<br><br>
<span class="comment">// ── خروجی Markdown ──</span><br>
<span class="key">function</span> <span class="key">exportMarkdown</span>(boardFilter) { ... }<br><br>
<span class="comment">// ── خروجی CSV ──</span><br>
<span class="key">function</span> <span class="key">exportCSV</span>(boardFilter) { ... }
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">ط) متدهای یکپارچگی و Webhook</h4>
<div class="code-box">
<span class="comment">// ── ارسال رویداد به Webhook ──</span><br>
<span class="key">function</span> <span class="key">sendWebhook</span>(eventType, data) { ... }<br><br>
<span class="comment">// ── تست اتصال Webhook ──</span><br>
<span class="key">function</span> <span class="key">testWebhook</span>() { ... }
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">ی) متدهای رابط کاربری و تعامل</h4>
<div class="code-box">
<span class="comment">// ── سیستم Modal ──</span><br>
<span class="key">function</span> <span class="key">openModal</span>(modalId) { ... }<br>
<span class="key">function</span> <span class="key">closeModal</span>(modalId) { ... }<br><br>
<span class="comment">// ── اعلان‌های Toast ──</span><br>
<span class="key">function</span> <span class="key">showToast</span>(message, type) { ... }<br><br>
<span class="comment">// ── تغییر حالت تاریک/روشن ──</span><br>
<span class="key">function</span> <span class="key">toggleDarkMode</span>() { ... }<br><br>
<span class="comment">// ── مدیریت میانبرهای کیبورد ──</span><br>
<span class="key">function</span> <span class="key">handleKeyboardShortcuts</span>(event) { ... }
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">ک) متدهای کمکی</h4>
<div class="code-box">
<span class="comment">// ── تولید شناسه یکتا ──</span><br>
<span class="key">function</span> <span class="key">generateUUID</span>() { ... }<br><br>
<span class="comment">// ── محاسبه وضعیت سررسید کارت ──</span><br>
<span class="key">function</span> <span class="key">getCardDueStatus</span>(card) { ... }<br><br>
<span class="comment">// ── محاسبه آمار برد ──</span><br>
<span class="key">function</span> <span class="key">calculateBoardStats</span>() { ... }<br><br>
<span class="comment">// ── دانلود فایل ──</span><br>
<span class="key">function</span> <span class="key">downloadFile</span>(content, filename, mimeType) { ... }
</div>

<div class="info-box">
<strong>💡 اصل طراحی کدها:</strong> تمامی متدها بر اساس اصل <strong>Single Responsibility</strong> طراحی شده‌اند. هر متد دقیقاً یک وظیفه مشخص دارد و از ترکیب آن‌ها، رفتارهای پیچیده‌تر ساخته می‌شود. بدنه متدها در این مستند با <code>...</code> خلاصه شده تا تمرکز بر ساختار و معماری باقی بماند.
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">جریان داده در سیستم:</h4>
<table class="styled-table">
<thead>
<tr>
<th>مرحله</th>
<th>عملیات</th>
<th>متد درگیر</th>
</tr>
</thead>
<tbody>
<tr>
<td>۱</td>
<td>کاربر عملیاتی انجام می‌دهد (مثلاً ایجاد کارت)</td>
<td><code>createCard()</code></td>
</tr>
<tr>
<td>۲</td>
<td>وضعیت State به‌روزرسانی می‌شود</td>
<td>مستقیم روی State Container</td>
</tr>
<tr>
<td>۳</td>
<td>رابط کاربری مجدداً رندر می‌شود</td>
<td><code>render()</code></td>
</tr>
<tr>
<td>۴</td>
<td>ذخیره خودکار زمان‌بندی می‌شود</td>
<td><code>scheduleAutoSave()</code></td>
</tr>
<tr>
<td>۵</td>
<td>در صورت فعال بودن، Webhook ارسال می‌شود</td>
<td><code>sendWebhook()</code></td>
</tr>
<tr>
<td>۶</td>
<td>اعلان تأیید به کاربر نمایش داده می‌شود</td>
<td><code>showToast()</code></td>
</tr>
</tbody>
</table>
</div>
</div>

<!-- ═══════════════════════════════════════════════════════ -->
<!-- بخش ۵: مخاطبان هدف -->
<!-- ═══════════════════════════════════════════════════════ -->
<div class="section">
<div class="section-header">
<div class="section-number">۵</div>
<div class="section-title">
<h3>مخاطبان هدف: سازمان‌ها و تیم‌های مناسب</h3>
<div class="subtitle">Target Audience & Ideal Use Cases</div>
</div>
</div>
<div class="section-content">
<p>xKanban برای طیف گسترده‌ای از سازمان‌ها و تیم‌ها مناسب است. در ادامه، مخاطبان ایده‌آل این ابزار بر اساس ویژگی‌ها و نیازمندی‌هایشان دسته‌بندی شده‌اند:</p>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">الف) بر اساس نوع سازمان:</h4>

<table class="styled-table">
<thead>
<tr>
<th>نوع سازمان</th>
<th>چرا xKanban مناسب است؟</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>استارتاپ‌ها</strong></td>
<td>بودجه محدود، نیاز به شروع سریع، عدم تمایل به وابستگی به ابزارهای گران‌قیمت SaaS</td>
</tr>
<tr>
<td><strong>شرکت‌های کوچک و متوسط</strong></td>
<td>فقدان تیم IT اختصاصی برای نگهداری سرور، نیاز به ابزار ساده ولی حرفه‌ای</td>
</tr>
<tr>
<td><strong>سازمان‌های دولتی و نظامی</strong></td>
<td>الزامات سخت‌گیرانه امنیتی، ممنوعیت ارسال داده به سرورهای خارجی، نیاز به کارکرد آفلاین</td>
</tr>
<tr>
<td><strong>مراکز تحقیقاتی و دانشگاهی</strong></td>
<td>مدیریت پروژه‌های پژوهشی، کار در محیط‌های ایزوله شبکه‌ای، بودجه محدود</td>
</tr>
<tr>
<td><strong>شرکت‌های مشاوره‌ای</strong></td>
<td>مدیریت همزمان چندین پروژه مشتری، نیاز به قابلیت حمل و ارائه گزارش</td>
</tr>
<tr>
<td><strong>تیم‌های توزیع‌شده و ریموت</strong></td>
<td>اشتراک‌گذاری آسان پروژه از طریق فایل، عدم وابستگی به اتصال اینترنت پایدار</td>
</tr>
</tbody>
</table>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">ب) بر اساس نوع تیم:</h4>

<div class="feature-grid">
<div class="feature-card">
<div class="feature-icon">💻</div>
<h5>تیم‌های توسعه نرم‌افزار</h5>
<p>مدیریت اسپرینت‌ها، پیگیری باگ‌ها، مدیریت وابستگی‌های فنی و یکپارچگی با ابزارهای CI/CD از طریق Webhook</p>
</div>
<div class="feature-card">
<div class="feature-icon">🎨</div>
<h5>تیم‌های طراحی و خلاقیت</h5>
<p>پیگیری مراحل طراحی، مدیریت بازخوردها، رنگ‌بندی بصری برای دسته‌بندی پروژه‌ها</p>
</div>
<div class="feature-card">
<div class="feature-icon">📢</div>
<h5>تیم‌های بازاریابی و محتوا</h5>
<p>مدیریت تقویم محتوایی، پیگیری کمپین‌ها، تخصیص وظایف به نویسندگان و طراحان</p>
</div>
<div class="feature-card">
<div class="feature-icon">🏗️</div>
<h5>تیم‌های عملیات و زیرساخت</h5>
<p>مدیریت تیکت‌ها، پیگیری حوادث، ثبت تاریخچه جابجایی برای گزارش‌گیری</p>
</div>
<div class="feature-card">
<div class="feature-icon">🎓</div>
<h5>تیم‌های آموزشی و آکادمیک</h5>
<p>مدیریت پروژه‌های دانشجویی، پیگیری تکالیف، کار در محیط‌های بدون اینترنت</p>
</div>
<div class="feature-card">
<div class="feature-icon">🧑‍💼</div>
<h5>فریلنسرها و مشاوران مستقل</h5>
<p>مدیریت چندین پروژه مشتری، قابلیت حمل، عدم هزینه اشتراک ماهیانه</p>
</div>
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">ج) سناریوهای ایده‌آل استفاده:</h4>

<div class="comparison-grid">
<div class="comparison-card recommended">
<span class="card-badge">ایده‌آل ✓</span>
<h4>محیط‌های با محدودیت امنیتی</h4>
<ul>
<li><span>شبکه ایزوله</span> <span class="value">پشتیبانی کامل</span></li>
<li><span>بدون اینترنت</span> <span class="value">پشتیبانی کامل</span></li>
<li><span>داده حساس</span> <span class="value">ذخیره محلی</span></li>
<li><span>بدون سرور</span> <span class="value">بدون نیاز</span></li>
</ul>
</div>
<div class="comparison-card recommended">
<span class="card-badge">ایده‌آل ✓</span>
<h4>تیم‌های چابک کوچک</h4>
<ul>
<li><span>اندازه تیم</span> <span class="value">۲ تا ۱۵ نفر</span></li>
<li><span>متدولوژی</span> <span class="value">کانبان / اسکرام</span></li>
<li><span>شروع سریع</span> <span class="value">آنی</span></li>
<li><span>هزینه</span> <span class="value">صفر</span></li>
</ul>
</div>
</div>

<div class="info-box">
<strong>💡 نکته مهم:</strong> xKanban در حال حاضر برای استفاده فردی یا تیم‌های کوچک تا متوسط طراحی شده است. برای سازمان‌های بزرگ با نیاز به همزمانی چندکاربره بلادرنگ، نسخه فعلی به عنوان ابزار مکمل در کنار سیستم‌های سازمانی قابل استفاده است و نسخه چندکاربره در نقشه راه آینده قرار دارد.
</div>
</div>
</div>

<!-- ═══════════════════════════════════════════════════════ -->
<!-- بخش ۶: جمع‌بندی -->
<!-- ═══════════════════════════════════════════════════════ -->
<div class="section">
<div class="section-header">
<div class="section-number">۶</div>
<div class="section-title">
<h3>جمع‌بندی نهایی</h3>
<div class="subtitle">Final Summary</div>
</div>
</div>
<div class="section-content">
<p>xKanban پاسخی مهندسی‌شده به معضلات واقعی مدیریت پروژه در دنیای مدرن است. این ابزار با حذف وابستگی به سرور، تضمین مالکیت داده‌ها، ارائه قابلیت حمل بی‌نظیر و حفظ غنای عملکردی، جایگاه منحصربه‌فردی در اکوسیستم ابزارهای مدیریت پروژه دارد.</p>

<div class="conclusion-box">
<h3>🎯 ارزش پیشنهادی xKanban در یک نگاه</h3>
<p>ابزاری حرفه‌ای، مستقل، امن و قابل حمل برای مدیریت پروژه‌های کانبان که بدون هیچ هزینه‌ای، بدون نیاز به سرور و بدون وابستگی به اینترنت، در اختیار تیم شما قرار می‌گیرد.</p>
<div class="highlight">
<strong>💎 فرمول ارزش xKanban:</strong><br>
استقلال از زیرساخت + مالکیت کامل داده + قابلیت حمل + غنای عملکردی + سادگی = <strong>آزادی عمل مطلق تیم</strong>
</div>
</div>

<div class="info-box">
<strong>📞 گام بعدی:</strong> برای ارزیابی عملی xKanban در محیط سازمان خود، می‌توانید فایل HTML را مستقیماً در مرورگر باز کرده و بدون هیچ پیکربندی، تجربه کاربری واقعی را لمس کنید. جهت سفارشی‌سازی یا توسعه ویژگی‌های اختصاصی، آماده برگزاری جلسه فنی هستیم.
</div>
</div>
</div>

<div class="page-info">
این مستند محرمانه بوده و صرفاً جهت استفاده داخلی و ارائه به ذی‌نفعان تهیه شده است. هرگونه کپی‌برداری یا افشا بدون اجازه کتبی ممنوع است.
</div>
</div>

<footer class="document-footer">
<div class="footer-brand">طراحی، تحلیل و توسعه: هادی خزاعی اصل</div>
<div style="font-size: 11px; opacity: 0.8; margin-bottom: 5px;">معمار و توسعه‌دهنده ارشد نرم‌افزار | متخصص .NET و Angular</div>
<div class="footer-divider"></div>
<div>تاریخ تهیه: یکشنبه ۲۲ شهریور ۱۴۰۵ (۱۳ سپتامبر ۲۰۲۶) &nbsp;|&nbsp; ساعت: ۱۱:۳۰ &nbsp;|&nbsp; نسخه: ۱.۰.۰ &nbsp;|&nbsp; طبقه‌بندی: داخلی - محصولی</div>
</footer>
</div>
</body>
</html>

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Resources\Tools\Document\xKanban_Case_Study.html

<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>xKanban | مستند جامع معرفی محصول و معماری فنی</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap');
:root {
--primary: #1a365d;
--primary-light: #2c5282;
--accent: #c9a961;
--accent-dark: #a8893f;
--bg: #faf9f6;
--card-bg: #ffffff;
--text: #1a202c;
--text-muted: #4a5568;
--border: #e2e8f0;
--success: #38a169;
--danger: #e53e3e;
--warning: #dd6b20;
--shadow: 0 4px 6px rgba(0,0,0,0.05), 0 10px 30px rgba(26,54,93,0.08);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Vazirmatn', Tahoma, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.9;
font-size: 15px;
padding: 30px 15px;
}
.document-container {
max-width: 950px;
margin: 0 auto;
background: var(--card-bg);
box-shadow: var(--shadow);
border-radius: 12px;
overflow: hidden;
}
.document-header {
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);
color: white;
padding: 50px 40px 40px;
position: relative;
border-bottom: 4px solid var(--accent);
}
.document-header::before {
content: '';
position: absolute;
top: 0; right: 0;
width: 200px; height: 200px;
background: radial-gradient(circle, rgba(201,169,97,0.15) 0%, transparent 70%);
border-radius: 50%;
transform: translate(50%, -50%);
}
.header-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
position: relative;
z-index: 1;
}
.company-badge {
display: flex;
align-items: center;
gap: 12px;
}
.company-logo {
width: 60px; height: 60px;
background: var(--accent);
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
.doc-meta {
text-align: left;
font-size: 12px;
opacity: 0.9;
}
.doc-meta div { margin-bottom: 3px; }
.document-title {
text-align: center;
position: relative;
z-index: 1;
padding: 20px 0;
}
.document-title .label {
display: inline-block;
background: rgba(201,169,97,0.2);
color: var(--accent);
padding: 4px 16px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
margin-bottom: 15px;
border: 1px solid rgba(201,169,97,0.4);
}
.document-title h1 {
font-size: 28px;
font-weight: 800;
margin-bottom: 10px;
letter-spacing: -0.5px;
}
.document-title h2 {
font-size: 16px;
font-weight: 400;
opacity: 0.9;
}
.document-body {
padding: 40px;
}
.intro-text {
background: linear-gradient(to left, #f7fafc, #edf2f7);
border-right: 4px solid var(--accent);
padding: 20px 25px;
border-radius: 8px;
margin-bottom: 35px;
font-size: 14px;
color: var(--text-muted);
}
.section {
margin-bottom: 40px;
page-break-inside: avoid;
}
.section-header {
display: flex;
align-items: center;
gap: 15px;
padding-bottom: 12px;
border-bottom: 2px solid var(--border);
margin-bottom: 20px;
}
.section-number {
width: 50px; height: 50px;
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 800;
font-size: 20px;
flex-shrink: 0;
box-shadow: 0 4px 10px rgba(26,54,93,0.2);
}
.section-title { flex: 1; }
.section-title h3 {
font-size: 19px;
font-weight: 700;
color: var(--primary);
margin-bottom: 2px;
}
.section-title .subtitle {
font-size: 12px;
color: var(--text-muted);
}
.section-content {
padding-right: 10px;
}
.section-content p {
margin-bottom: 12px;
text-align: justify;
text-indent: 20px;
}
.section-content p.no-indent { text-indent: 0; }
.highlight-box {
background: linear-gradient(to left, #fffaf0, #fef5e7);
border: 1px solid #f6e05e;
border-right: 4px solid var(--accent);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.highlight-box strong { color: var(--accent-dark); }
.info-box {
background: linear-gradient(to left, #ebf8ff, #e6f6ff);
border: 1px solid #90cdf4;
border-right: 4px solid var(--primary-light);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.success-box {
background: linear-gradient(to left, #f0fff4, #e6fffa);
border: 1px solid #9ae6b4;
border-right: 4px solid var(--success);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.warning-box {
background: linear-gradient(to left, #fffaf0, #fef5e7);
border: 1px solid #fbd38d;
border-right: 4px solid var(--warning);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.code-box {
background: #1a202c;
color: #e2e8f0;
border-right: 4px solid var(--accent);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 13px;
font-family: 'Consolas', 'Courier New', monospace;
direction: ltr;
text-align: left;
overflow-x: auto;
line-height: 1.7;
}
.code-box .key { color: #c9a961; }
.code-box .str { color: #68d391; }
.code-box .num { color: #63b3ed; }
.code-box .comment { color: #718096; font-style: italic; }
.code-box .fn { color: #f6ad55; }
.code-box .kw { color: #fc8181; }
.styled-table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
}
.styled-table thead {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
}
.styled-table th {
padding: 14px 16px;
text-align: right;
font-weight: 600;
font-size: 13px;
}
.styled-table td {
padding: 13px 16px;
border-bottom: 1px solid var(--border);
font-size: 13px;
}
.styled-table tbody tr:last-child td { border-bottom: none; }
.styled-table tbody tr:nth-child(even) { background: #f8fafc; }
.styled-table .highlight-row {
background: #fffaf0 !important;
font-weight: 600;
}
.comparison-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin: 25px 0;
}
.comparison-card {
background: #f8fafc;
border: 2px solid var(--border);
border-radius: 10px;
padding: 20px;
position: relative;
}
.comparison-card.recommended {
border-color: var(--success);
background: linear-gradient(to left, #f0fff4, #e6fffa);
}
.comparison-card .card-badge {
position: absolute;
top: -10px;
right: 20px;
background: var(--success);
color: white;
padding: 3px 12px;
border-radius: 15px;
font-size: 11px;
font-weight: 600;
}
.comparison-card h4 {
font-size: 16px;
font-weight: 700;
color: var(--primary);
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 2px solid var(--border);
}
.comparison-card.recommended h4 { border-bottom-color: var(--success); }
.comparison-card ul { list-style: none; padding: 0; }
.comparison-card ul li {
padding: 8px 0;
border-bottom: 1px solid var(--border);
font-size: 13px;
display: flex;
justify-content: space-between;
}
.comparison-card ul li:last-child { border-bottom: none; }
.comparison-card ul li .value { font-weight: 600; color: var(--primary); }
.quote-box {
background: linear-gradient(135deg, #f7fafc, #edf2f7);
border-right: 4px solid var(--accent);
padding: 20px 25px;
border-radius: 8px;
margin: 20px 0;
position: relative;
}
.quote-box::before {
content: '"';
position: absolute;
top: -10px;
right: 15px;
font-size: 60px;
color: var(--accent);
opacity: 0.3;
font-family: Georgia, serif;
}
.quote-box p {
font-style: italic;
color: var(--text-muted);
margin-bottom: 10px;
text-indent: 0 !important;
}
.quote-box .source {
font-size: 12px;
color: var(--text-muted);
text-align: left;
font-style: normal;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
margin: 25px 0;
}
.stat-card {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
padding: 20px;
border-radius: 10px;
text-align: center;
box-shadow: 0 4px 12px rgba(26,54,93,0.2);
}
.stat-card .stat-value {
font-size: 24px;
font-weight: 800;
margin-bottom: 5px;
color: var(--accent);
line-height: 1.3;
}
.stat-card .stat-label { font-size: 12px; opacity: 0.9; }
.feature-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
margin: 25px 0;
}
.feature-card {
background: #f8fafc;
border: 1px solid var(--border);
border-right: 4px solid var(--primary-light);
padding: 18px;
border-radius: 8px;
}
.feature-card .feature-icon { font-size: 24px; margin-bottom: 10px; }
.feature-card h5 {
font-size: 15px;
font-weight: 700;
color: var(--primary);
margin-bottom: 8px;
}
.feature-card p {
font-size: 13px;
color: var(--text-muted);
text-indent: 0 !important;
margin-bottom: 0 !important;
}
.architecture-diagram {
background: linear-gradient(to left, #f7fafc, #edf2f7);
border: 2px solid var(--border);
border-radius: 10px;
padding: 25px;
margin: 25px 0;
text-align: center;
}
.arch-layer {
background: white;
border: 2px solid var(--primary-light);
border-radius: 8px;
padding: 15px;
margin: 10px auto;
max-width: 750px;
box-shadow: 0 2px 6px rgba(0,0,0,0.05);
}
.arch-layer-title {
font-weight: 700;
color: var(--primary);
margin-bottom: 10px;
font-size: 14px;
border-bottom: 2px solid var(--accent);
padding-bottom: 5px;
display: inline-block;
}
.arch-components {
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: center;
}
.arch-chip {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
padding: 6px 14px;
border-radius: 15px;
font-size: 12px;
font-weight: 500;
}
.arch-chip.gold {
background: linear-gradient(135deg, var(--accent-dark), var(--accent));
color: #1a202c;
}
.arch-arrow {
text-align: center;
font-size: 24px;
color: var(--accent);
margin: 5px 0;
}
.conclusion-box {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
padding: 30px;
border-radius: 12px;
margin: 30px 0;
text-align: center;
box-shadow: 0 8px 20px rgba(26,54,93,0.3);
}
.conclusion-box h3 {
font-size: 22px;
font-weight: 800;
margin-bottom: 15px;
color: var(--accent);
}
.conclusion-box p {
font-size: 14px;
line-height: 1.8;
text-indent: 0 !important;
margin-bottom: 15px;
}
.conclusion-box .highlight {
background: rgba(201,169,97,0.2);
padding: 15px 20px;
border-radius: 8px;
margin-top: 20px;
border: 1px solid rgba(201,169,97,0.4);
}
.document-footer {
background: var(--primary);
color: white;
padding: 25px 40px;
text-align: center;
font-size: 12px;
margin-top: 40px;
}
.document-footer .footer-brand {
font-weight: 700;
color: var(--accent);
margin-bottom: 5px;
font-size: 14px;
}
.footer-divider {
width: 60px;
height: 2px;
background: var(--accent);
margin: 10px auto;
}
.page-info {
text-align: center;
padding: 15px;
font-size: 11px;
color: var(--text-muted);
border-top: 1px solid var(--border);
margin-top: 30px;
}
.toc {
background: #f8fafc;
border: 1px solid var(--border);
border-radius: 10px;
padding: 25px 30px;
margin-bottom: 35px;
}
.toc h4 {
font-size: 16px;
font-weight: 700;
color: var(--primary);
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 2px solid var(--accent);
}
.toc ol {
padding-right: 20px;
}
.toc ol li {
padding: 6px 0;
font-size: 14px;
color: var(--text-muted);
}
.toc ol li strong { color: var(--primary); }
.value-card {
background: linear-gradient(135deg, #f0fff4, #e6fffa);
border: 1px solid #9ae6b4;
border-radius: 10px;
padding: 20px;
margin-bottom: 12px;
display: flex;
gap: 15px;
align-items: flex-start;
}
.value-card .value-icon {
font-size: 28px;
flex-shrink: 0;
margin-top: 3px;
}
.value-card .value-body h5 {
font-size: 15px;
font-weight: 700;
color: var(--primary);
margin-bottom: 6px;
}
.value-card .value-body p {
font-size: 13px;
color: var(--text-muted);
text-indent: 0 !important;
margin-bottom: 0 !important;
}
.audience-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
margin: 25px 0;
}
.audience-card {
background: white;
border: 2px solid var(--border);
border-radius: 10px;
padding: 20px;
text-align: center;
transition: all 0.2s;
}
.audience-card:hover {
border-color: var(--accent);
box-shadow: 0 4px 12px rgba(201,169,97,0.15);
}
.audience-card .audience-icon { font-size: 36px; margin-bottom: 10px; }
.audience-card h5 {
font-size: 15px;
font-weight: 700;
color: var(--primary);
margin-bottom: 8px;
}
.audience-card p {
font-size: 12px;
color: var(--text-muted);
text-indent: 0 !important;
margin-bottom: 0 !important;
}
.method-list {
list-style: none;
padding: 0;
margin: 15px 0;
}
.method-list li {
background: #f8fafc;
border-right: 3px solid var(--primary-light);
padding: 10px 16px;
margin-bottom: 6px;
border-radius: 6px;
font-size: 13px;
font-family: 'Consolas', 'Courier New', monospace;
direction: ltr;
text-align: left;
display: flex;
justify-content: space-between;
align-items: center;
}
.method-list li .method-name {
color: var(--primary);
font-weight: 600;
}
.method-list li .method-desc {
font-family: 'Vazirmatn', Tahoma, sans-serif;
direction: rtl;
text-align: right;
color: var(--text-muted);
font-size: 12px;
}
@media print {
body { background: white; padding: 0; }
.document-container { box-shadow: none; border-radius: 0; }
.section { page-break-inside: avoid; }
}
@media (max-width: 768px) {
.document-body { padding: 25px 20px; }
.document-header { padding: 30px 20px; }
.comparison-grid { grid-template-columns: 1fr; }
.stats-grid { grid-template-columns: 1fr; }
.feature-grid { grid-template-columns: 1fr; }
.audience-grid { grid-template-columns: 1fr; }
.header-top { flex-direction: column; gap: 15px; }
.doc-meta { text-align: center; }
}
</style>
</head>
<body>
<div class="document-container">

<!-- ═══════════════ HEADER ═══════════════ -->
<header class="document-header">
<div class="header-top">
<div class="company-badge">
<div class="company-logo"></div>
</div>
<div class="doc-meta">
<div>شماره مستند: PROD-XKN-1405-002</div>
<div>نسخه: ۱.۰.۰</div>
<div>تاریخ تهیه: یکشنبه ۲۲ شهریور ۱۴۰۵ (۱۳ سپتامبر ۲۰۲۶)</div>
<div>ساعت: ۱۰:۳۰</div>
<div>طبقه‌بندی: عمومی - معرفی محصول</div>
</div>
</div>
<div class="document-title">
<span class="label">PRODUCT DOCUMENTATION</span>
<h1>xKanban | ابزار مدیریت بردهای کانبان</h1>
<h2>معرفی محصول، تحلیل ارزش سازمانی و معماری فنی</h2>
</div>
</header>

<!-- ═══════════════ BODY ═══════════════ -->
<div class="document-body">

<div class="intro-text">
<strong>مخاطبان مستند:</strong> مدیران فناوری اطلاعات، مدیران پروژه، سرپرستان تیم‌های توسعه، معماران نرم‌افزار و تصمیم‌گیرندگان سازمانی<br>
این مستند به معرفی جامع ابزار <strong>xKanban</strong> می‌پردازد؛ ابزاری که از دل نیازهای واقعی تیم‌های چابک متولد شده و با هدف حذف پیچیدگی‌های غیرضروری ابزارهای مدیریت پروژه طراحی شده است. در این مستند، از چرایی شکل‌گیری محصول تا معماری فنی و ارزش‌های سازمانی آن، همه‌چیز تشریح شده است.
</div>

<!-- فهرست مطالب -->
<div class="toc">
<h4>📑 فهرست مطالب</h4>
<ol>
<li><strong>شرح مسئله و چرایی خلق xKanban</strong></li>
<li><strong>مزایای کلیدی محصول</strong></li>
<li><strong>ارزش‌های سازمانی خلق‌شده</strong></li>
<li><strong>معماری فنی پروژه</strong></li>
<li><strong>مناسب برای چه سازمان‌ها و تیم‌هایی؟</strong></li>
<li><strong>جمع‌بندی و گام‌های بعدی</strong></li>
</ol>
</div>

<!-- ═══════════════ بخش ۱: شرح مسئله ═══════════════ -->
<div class="section">
<div class="section-header">
<div class="section-number">۱</div>
<div class="section-title">
<h3>شرح مسئله و چرایی خلق xKanban</h3>
<div class="subtitle">Problem Statement & Product Genesis</div>
</div>
</div>
<div class="section-content">

<p>در دنیای امروز، تقریباً هر تیمی — از توسعه نرم‌افزار گرفته تا بازاریابی، منابع انسانی و عملیات — با یک چالش مشترک دست‌وپنجه نرم می‌کند: <strong>چگونه کارها را به‌صورت شفاف، منظم و قابل ردیابی مدیریت کنیم؟</strong> متدولوژی کانبان به‌عنوان یکی از مؤثرترین رویکردهای مدیریت بصری کار، سال‌هاست که پاسخ این سؤال را ارائه داده است؛ اما ابزارهای موجود برای پیاده‌سازی این متدولوژی، خود به منبع جدیدی از پیچیدگی تبدیل شده‌اند.</p>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">معضلات رایج در اکوسیستم ابزارهای فعلی:</h4>

<div class="warning-box">
<strong>⚠️ چالش‌هایی که تیم‌ها هر روز با آن‌ها مواجه‌اند:</strong>
<ul style="padding-right: 20px; margin-top: 10px;">
<li style="margin-bottom: 8px;"><strong>وابستگی به اینترنت و سرور:</strong> بسیاری از ابزارها نیازمند اتصال دائمی به اینترنت و سرورهای ابری هستند. در محیط‌هایی با محدودیت شبکه یا الزامات امنیتی بالا، این وابستگی یک مانع جدی است.</li>
<li style="margin-bottom: 8px;"><strong>هزینه‌های اشتراک ماهانه:</strong> ابزارهای تجاری برای هر کاربر هزینه اشتراک دریافت می‌کنند. برای تیم‌های بزرگ، این هزینه به‌سرعت به رقم قابل‌توجهی تبدیل می‌شود.</li>
<li style="margin-bottom: 8px;"><strong>پیچیدگی بیش‌ازحد:</strong> ابزارهایی مانند Jira یا Azure DevOps صدها قابلیت دارند که اکثر تیم‌ها از کمتر از ۲۰ درصد آن‌ها استفاده می‌کنند. این پیچیدگی، منحنی یادگیری را بالا برده و بهره‌وری را کاهش می‌دهد.</li>
<li style="margin-bottom: 8px;"><strong>فرآیند نصب و راه‌اندازی سنگین:</strong> برخی ابزارها نیازمند نصب سرور، پیکربندی دیتابیس و مدیریت زیرساخت هستند که زمان و تخصص قابل‌توجهی می‌طلبد.</li>
<li style="margin-bottom: 8px;"><strong>قفل‌شدگی داده‌ها (Vendor Lock-in):</strong> داده‌های پروژه در سرورهای ارائه‌دهنده سرویس ذخیره می‌شوند و خروج از آن اکوسیستم، پرهزینه و دشوار است.</li>
<li style="margin-bottom: 8px;"><strong>عدم قابلیت حمل:</strong> انتقال پروژه از یک محیط به محیط دیگر (مثلاً از لپ‌تاپ شخصی به سیستم شرکت) در بسیاری از ابزارها فرآیندی پیچیده است.</li>
</ul>
</div>

<p>این معضلات، یک شکاف آشکار در بازار ابزارهای مدیریت پروژه ایجاد کرده بود: <strong>جای خالی ابزاری ساده، مستقل، رایگان و در عین حال حرفه‌ای</strong> که بدون هیچ پیش‌نیازی قابل استفاده باشد و در عین حال، قابلیت‌های پیشرفته‌ای مانند فیلتر چندبعدی، تاریخچه تغییرات، وابستگی بین کارت‌ها و یکپارچگی با سیستم‌های خارجی را ارائه دهد.</p>

<div class="highlight-box">
<strong>🎯 نقطه عطف:</strong> xKanban دقیقاً از همین شکاف متولد شد. هدف، خلق ابزاری بود که «صفر تا صد» مدیریت کانبان را بدون هیچ وابستگی خارجی، در یک فایل واحد و قابل حمل ارائه دهد — ابزاری که در کمتر از ۳ ثانیه باز شود و از همان لحظه اول قابل استفاده باشد.
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">نیازمندی‌های کلیدی که xKanban برای پاسخ به آن‌ها طراحی شد:</h4>
<table class="styled-table">
<thead>
<tr>
<th>ردیف</th>
<th>نیازمندی</th>
<th>وضعیت در ابزارهای موجود</th>
<th>پاسخ xKanban</th>
</tr>
</thead>
<tbody>
<tr>
<td>۱</td>
<td>اجرای آفلاین و بدون سرور</td>
<td style="color: var(--danger);">اکثراً ناممکن</td>
<td style="color: var(--success); font-weight: 600;">✓ کاملاً آفلاین</td>
</tr>
<tr>
<td>۲</td>
<td>بدون هزینه اشتراک</td>
<td style="color: var(--danger);">اشتراک ماهانه/سالانه</td>
<td style="color: var(--success); font-weight: 600;">✓ کاملاً رایگان</td>
</tr>
<tr>
<td>۳</td>
<td>راه‌اندازی فوری</td>
<td style="color: var(--warning);">نیاز به نصب و پیکربندی</td>
<td style="color: var(--success); font-weight: 600;">✓ یک کلیک</td>
</tr>
<tr>
<td>۴</td>
<td>قابلیت حمل داده‌ها</td>
<td style="color: var(--warning);">محدود و پیچیده</td>
<td style="color: var(--success); font-weight: 600;">✓ فایل JSON</td>
</tr>
<tr>
<td>۵</td>
<td>سادگی همراه با قدرت</td>
<td style="color: var(--danger);">یا ساده یا پیچیده</td>
<td style="color: var(--success); font-weight: 600;">✓ تعادل بهینه</td>
</tr>
<tr>
<td>۶</td>
<td>مالکیت کامل داده‌ها</td>
<td style="color: var(--danger);">ذخیره در سرور ثالث</td>
<td style="color: var(--success); font-weight: 600;">✓ کاملاً محلی</td>
</tr>
</tbody>
</table>

</div>
</div>

<!-- ═══════════════ بخش ۲: مزایای کلیدی ═══════════════ -->
<div class="section">
<div class="section-header">
<div class="section-number">۲</div>
<div class="section-title">
<h3>مزایای کلیدی محصول</h3>
<div class="subtitle">Key Product Advantages</div>
</div>
</div>
<div class="section-content">

<p>xKanban مجموعه‌ای از مزایای منحصربه‌فرد را ارائه می‌دهد که آن را از تمامی ابزارهای موجود در بازار متمایز می‌سازد. این مزایا نه صرفاً ویژگی‌های فنی، بلکه <strong>تصمیمات استراتژیک طراحی</strong> هستند که هر کدام پاسخی مستقیم به یکی از معضلات مطرح‌شده در بخش قبل می‌باشند.</p>

<div class="stats-grid">
<div class="stat-card">
<div class="stat-value">۰ ثانیه<br>نصب</div>
<div class="stat-label">بدون نیاز به هیچ فرآیند نصب یا پیکربندی</div>
</div>
<div class="stat-card">
<div class="stat-value">۱۰۰٪<br>آفلاین</div>
<div class="stat-label">اجرای کامل بدون نیاز به اینترنت یا سرور</div>
</div>
<div class="stat-card">
<div class="stat-value">۱ فایل<br>واحد</div>
<div class="stat-label">تمام محصول در یک فایل HTML مستقل</div>
</div>
</div>

<div class="feature-grid">
<div class="feature-card">
<div class="feature-icon">🚀</div>
<h5>راه‌اندازی آنی</h5>
<p>فایل HTML را در مرورگر باز کنید. تمام. هیچ سروری، هیچ دیتابیسی، هیچ اکانتی لازم نیست. در کمتر از ۳ ثانیه آماده استفاده هستید.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🔒</div>
<h5>حریم خصوصی مطلق</h5>
<p>تمام داده‌ها در مرورگر شما باقی می‌مانند. هیچ اطلاعاتی به هیچ سرور خارجی ارسال نمی‌شود. مالکیت داده‌ها ۱۰۰٪ در اختیار شماست.</p>
</div>
<div class="feature-card">
<div class="feature-icon">📦</div>
<h5>قابلیت حمل بی‌نظیر</h5>
<p>پروژه خود را به‌صورت فایل JSON ذخیره کنید و روی فلش، ایمیل یا هر فضای ابری منتقل کنید. در هر سیستمی قابل بارگذاری است.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🎨</div>
<h5>رابط کاربری مدرن و حرفه‌ای</h5>
<p>طراحی بصری جذاب با پشتیبانی از حالت تاریک/روشن، رنگ‌بندی سفارشی برای بردها، ستون‌ها و کارت‌ها و انیمیشن‌های روان.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🔍</div>
<h5>فیلتر چندبعدی پیشرفته</h5>
<p>فیلتر هم‌زمان بر اساس تگ، مسئول، وضعیت سررسید و جستجوی متنی. یافتن هر کارت در کمتر از یک ثانیه حتی در پروژه‌های بزرگ.</p>
</div>
<div class="feature-card">
<div class="feature-icon">📡</div>
<h5>یکپارچگی با Webhook</h5>
<p>ارسال خودکار رویدادهای پروژه به Slack، Teams یا هر سیستم خارجی. ساختار JSON استاندارد با eventId و timestamp.</p>
</div>
<div class="feature-card">
<div class="feature-icon">⌨️</div>
<h5>میانبرهای کیبورد حرفه‌ای</h5>
<p>بیش از ۸ میانبر کیبورد برای کاربران حرفه‌ای. از Ctrl+S برای ذخیره تا Alt+Arrow برای جابجایی سریع کارت‌ها بین ستون‌ها.</p>
</div>
<div class="feature-card">
<div class="feature-icon">📊</div>
<h5>آمار بلادرنگ پروژه</h5>
<p>مشاهده آنی تعداد کارت‌ها، درصد پیشرفت، کارت‌های عقب‌افتاده و نزدیک به موعد در نوار وضعیت پایین صفحه.</p>
</div>
</div>

<div class="success-box">
<strong>💎 مزیت رقابتی اصلی:</strong> xKanban تنها ابزاری است که ترکیب «سادگی یک فایل HTML» و «قدرت یک سیستم مدیریت پروژه حرفه‌ای» را به‌صورت هم‌زمان ارائه می‌دهد. هیچ ابزار دیگری در بازار این تعادل را برقرار نکرده است.
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">مقایسه با ابزارهای رایج بازار:</h4>
<div class="comparison-grid">
<div class="comparison-card">
<h4>ابزارهای تجاری (Jira, Trello, Asana)</h4>
<ul>
<li><span>هزینه</span> <span class="value" style="color: var(--danger);">اشتراک ماهانه بالا</span></li>
<li><span>نیاز به اینترنت</span> <span class="value" style="color: var(--danger);">الزامی</span></li>
<li><span>نصب و راه‌اندازی</span> <span class="value" style="color: var(--warning);">ثبت‌نام + پیکربندی</span></li>
<li><span>مالکیت داده</span> <span class="value" style="color: var(--danger);">سرور ثالث</span></li>
<li><span>پیچیدگی</span> <span class="value" style="color: var(--danger);">بسیار بالا</span></li>
<li><span>آفلاین</span> <span class="value" style="color: var(--danger);">ناممکن</span></li>
</ul>
</div>
<div class="comparison-card recommended">
<span class="card-badge">xKanban ✓</span>
<h4>xKanban</h4>
<ul>
<li><span>هزینه</span> <span class="value" style="color: var(--success);">کاملاً رایگان</span></li>
<li><span>نیاز به اینترنت</span> <span class="value" style="color: var(--success);">خیر</span></li>
<li><span>نصب و راه‌اندازی</span> <span class="value" style="color: var(--success);">یک کلیک</span></li>
<li><span>مالکیت داده</span> <span class="value" style="color: var(--success);">۱۰۰٪ محلی</span></li>
<li><span>پیچیدگی</span> <span class="value" style="color: var(--success);">بهینه و متعادل</span></li>
<li><span>آفلاین</span> <span class="value" style="color: var(--success);">کاملاً پشتیبانی</span></li>
</ul>
</div>
</div>

</div>
</div>

<!-- ═══════════════ بخش ۳: ارزش‌های سازمانی ═══════════════ -->
<div class="section">
<div class="section-header">
<div class="section-number">۳</div>
<div class="section-title">
<h3>ارزش‌های سازمانی خلق‌شده</h3>
<div class="subtitle">Organizational Value Proposition</div>
</div>
</div>
<div class="section-content">

<p>استفاده از xKanban در یک سازمان، فراتر از صرفاً «مدیریت کارها» است. این ابزار، ارزش‌های استراتژیک و عملیاتی ملموسی را در سطوح مختلف سازمان خلق می‌کند که در ادامه به تفصیل تشریح شده‌اند:</p>

<div class="value-card">
<div class="value-icon">💰</div>
<div class="value-body">
<h5>حذف کامل هزینه‌های لایسنس و اشتراک</h5>
<p>سازمان‌هایی که از ابزارهایی مانند Jira یا Azure DevOps استفاده می‌کنند، سالانه مبالغ قابل‌توجهی بابت لایسنس هر کاربر پرداخت می‌کنند. xKanban این هزینه را به صفر می‌رساند و بودجه آزادشده را برای سرمایه‌گذاری در حوزه‌های ارزشمندتر آزاد می‌کند.</p>
</div>
</div>

<div class="value-card">
<div class="value-icon">🔐</div>
<div class="value-body">
<h5>تطبیق با الزامات امنیتی و حریم خصوصی</h5>
<p>در سازمان‌هایی با حساسیت امنیتی بالا (نظامی، بانکی، بهداشتی)، ارسال داده‌های پروژه به سرورهای ابری خارجی ممنوع یا محدود است. xKanban با ذخیره‌سازی ۱۰۰٪ محلی، این الزامات را به‌صورت ذاتی رعایت می‌کند.</p>
</div>
</div>

<div class="value-card">
<div class="value-icon">⚡</div>
<div class="value-body">
<h5>کاهش زمان Onboarding اعضای جدید</h5>
<p>برخلاف ابزارهای پیچیده که آموزش آن‌ها هفته‌ها زمان می‌برد، xKanban به‌قدری ساده و شهودی است که یک عضو جدید تیم در کمتر از ۱۰ دقیقه با آن آشنا شده و شروع به کار می‌کند.</p>
</div>
</div>

<div class="value-card">
<div class="value-icon">📈</div>
<div class="value-body">
<h5>افزایش شفافیت و پاسخگویی در تیم</h5>
<p>با نمایش بصری وضعیت تمام کارها، تاریخچه جابجایی کارت‌ها و آمار بلادرنگ پیشرفت، هر عضو تیم و هر مدیری در هر لحظه دید کاملی از وضعیت پروژه دارد. این شفافیت، فرهنگ پاسخگویی را تقویت می‌کند.</p>
</div>
</div>

<div class="value-card">
<div class="value-icon">🔄</div>
<div class="value-body">
<h5>استانداردسازی فرآیندهای مدیریت پروژه</h5>
<p>xKanban تیم‌ها را به استفاده از یک متدولوژی استاندارد (کانبان) هدایت می‌کند. ستون‌های مشخص، جریان کار تعریف‌شده و تاریخچه تغییرات، همگی به ایجاد یک فرآیند تکرارپذیر و قابل اندازه‌گیری کمک می‌کنند.</p>
</div>
</div>

<div class="value-card">
<div class="value-icon">🛡️</div>
<div class="value-body">
<h5>حذف ریسک Vendor Lock-in</h5>
<p>داده‌های پروژه در فرمت استاندارد JSON ذخیره می‌شوند. این یعنی سازمان هیچ‌گاه در اکوسیستم یک ارائه‌دهنده خاص قفل نمی‌شود و در هر زمان می‌تواند داده‌های خود را به هر سیستم دیگری منتقل کند.</p>
</div>
</div>

<div class="value-card">
<div class="value-icon">🌐</div>
<div class="value-body">
<h5>یکپارچگی با اکوسیستم موجود سازمان</h5>
<p>از طریق مکانیزم Webhook، xKanban می‌تواند رویدادهای پروژه را به Slack، Microsoft Teams، سیستم‌های CI/CD یا هر API سفارشی ارسال کند. این یعنی xKanban به‌جای جایگزینی ابزارهای موجود، آن‌ها را تکمیل می‌کند.</p>
</div>
</div>

<div class="value-card">
<div class="value-icon">📉</div>
<div class="value-body">
<h5>کاهش سربار مدیریت زیرساخت</h5>
<p>بدون نیاز به سرور، دیتابیس، به‌روزرسانی‌های امنیتی و مانیتورینگ، تیم IT سازمان از بار مدیریت یک ابزار اضافی رها می‌شود. این صرفه‌جویی در زمان و منابع انسانی، ارزش پنهان اما بسیار قابل‌توجهی است.</p>
</div>
</div>

<div class="highlight-box">
<strong>📊 خلاصه ارزش‌آفرینی:</strong> xKanban در چهار بُعد اصلی برای سازمان ارزش خلق می‌کند: <strong>کاهش هزینه‌ها</strong> (حذف لایسنس و زیرساخت)، <strong>افزایش بهره‌وری</strong> (Onboarding سریع و رابط شهودی)، <strong>کاهش ریسک</strong> (امنیت داده و عدم وابستگی) و <strong>بهبود کیفیت فرآیندها</strong> (استانداردسازی و شفافیت).
</div>

</div>
</div>

<!-- ═══════════════ بخش ۴: معماری فنی ═══════════════ -->
<div class="section">
<div class="section-header">
<div class="section-number">۴</div>
<div class="section-title">
<h3>معماری فنی پروژه</h3>
<div class="subtitle">Technical Architecture & Code Structure</div>
</div>
</div>
<div class="section-content">

<p>xKanban به‌عنوان یک <strong>Single-Page Application (SPA)</strong> تک‌فایلی طراحی شده است که تمامی لایه‌های نمایش، منطق و داده را در یک فایل HTML واحد encapsulate می‌کند. این تصمیم معماری، اگرچه در نگاه اول غیرمعمول به نظر می‌رسد، اما با توجه به اهداف محصول (آفلاین بودن، قابلیت حمل و عدم وابستگی) منطقی‌ترین انتخاب ممکن بوده است.</p>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">الف) نمودار معماری لایه‌ای</h4>

<div class="architecture-diagram">
<div class="arch-layer">
<div class="arch-layer-title">🎨 Presentation Layer</div>
<div class="arch-components">
<span class="arch-chip">Toolbar UI</span>
<span class="arch-chip">Board Renderer</span>
<span class="arch-chip">Column Renderer</span>
<span class="arch-chip">Card Renderer</span>
<span class="arch-chip">Modal System</span>
<span class="arch-chip">Filter Panel</span>
<span class="arch-chip">Stats Bar</span>
<span class="arch-chip">Theme Engine</span>
</div>
</div>
<div class="arch-arrow">▼ Event Binding & State Observation ▼</div>
<div class="arch-layer">
<div class="arch-layer-title">⚙️ Application Logic Layer</div>
<div class="arch-components">
<span class="arch-chip gold">ProjectManager</span>
<span class="arch-chip gold">BoardManager</span>
<span class="arch-chip gold">ColumnManager</span>
<span class="arch-chip gold">CardManager</span>
<span class="arch-chip gold">TagManager</span>
<span class="arch-chip gold">FilterEngine</span>
<span class="arch-chip gold">WebhookService</span>
<span class="arch-chip gold">ShortcutHandler</span>
<span class="arch-chip gold">HistoryTracker</span>
</div>
</div>
<div class="arch-arrow">▼ Read / Write / Sync ▼</div>
<div class="arch-layer">
<div class="arch-layer-title">💾 Data & Persistence Layer</div>
<div class="arch-components">
<span class="arch-chip">AppState (In-Memory)</span>
<span class="arch-chip">LocalStorage Adapter</span>
<span class="arch-chip">AutoSave Engine</span>
<span class="arch-chip">JSON Serializer</span>
<span class="arch-chip">Export/Import Service</span>
<span class="arch-chip">Recovery Manager</span>
</div>
</div>
</div>

<h4 style="margin-top: 30px; color: var(--primary); font-size: 16px;">ب) مدل داده‌ای (Data Schema)</h4>
<p>ساختار داده‌ای پروژه از یک سلسله‌مراتب چهارسطحی پیروی می‌کند:</p>

<div class="code-box">
<span class="comment">// ساختار سلسله‌مراتبی داده‌ها</span><br>
<span class="key">Project</span><br>
&nbsp;&nbsp;├── <span class="key">id</span>: <span class="str">UUID</span><br>
&nbsp;&nbsp;├── <span class="key">title</span>: <span class="str">string</span><br>
&nbsp;&nbsp;├── <span class="key">description</span>: <span class="str">string</span><br>
&nbsp;&nbsp;├── <span class="key">createdAt</span>: <span class="str">ISO8601</span><br>
&nbsp;&nbsp;├── <span class="key">webhook</span>: { <span class="key">enabled</span>, <span class="key">url</span> }<br>
&nbsp;&nbsp;├── <span class="key">globalTags</span>: [<span class="key">Tag</span>]<br>
&nbsp;&nbsp;└── <span class="key">boards</span>: [<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="key">Board</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;├── <span class="key">id</span>, <span class="key">title</span>, <span class="key">backgroundColor</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;└── <span class="key">columns</span>: [<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="key">Column</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;├── <span class="key">id</span>, <span class="key">title</span>, <span class="key">order</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;└── <span class="key">cards</span>: [<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="key">Card</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;├── <span class="key">id</span>, <span class="key">title</span>, <span class="key">description</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;├── <span class="key">dueDate</span>, <span class="key">assignees</span>, <span class="key">tags</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;├── <span class="key">dependencies</span>, <span class="key">checklist</span><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;└── <span class="key">history</span>: [<span class="key">HistoryEntry</span>]<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;]<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;]<br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;]<br>
</div>

<h4 style="margin-top: 30px; color: var(--primary); font-size: 16px;">ج) ماژول‌های اصلی و متدهای کلیدی</h4>
<p>در ادامه، مهم‌ترین ماژول‌های سیستم به همراه متدهای کلیدی هر کدام تشریح شده‌اند. برای حفظ خوانایی، بدنه متدها خلاصه شده و تنها امضای آن‌ها نمایش داده شده است:</p>

<h5 style="margin-top: 20px; color: var(--primary-light); font-size: 14px;">📁 ماژول ProjectManager</h5>
<ul class="method-list">
<li>
<span class="method-name">initProject()</span>
<span class="method-desc">مقداردهی اولیه پروژه</span>
</li>
<li>
<span class="method-name">loadProject(data)</span>
<span class="method-desc">بارگذاری پروژه از فایل JSON</span>
</li>
<li>
<span class="method-name">saveProject(filename)</span>
<span class="method-desc">ذخیره پروژه به‌صورت فایل JSON</span>
</li>
<li>
<span class="method-name">updateProjectSettings(title, desc, webhook)</span>
<span class="method-desc">به‌روزرسانی تنظیمات پروژه</span>
</li>
<li>
<span class="method-name">getProjectStats()</span>
<span class="method-desc">محاسبه آمار کلی پروژه</span>
</li>
</ul>

<h5 style="margin-top: 20px; color: var(--primary-light); font-size: 14px;">📊 ماژول BoardManager</h5>
<ul class="method-list">
<li>
<span class="method-name">createBoard(title, description, bgColor)</span>
<span class="method-desc">ایجاد برد جدید</span>
</li>
<li>
<span class="method-name">editBoard(boardId, data)</span>
<span class="method-desc">ویرایش برد موجود</span>
</li>
<li>
<span class="method-name">deleteBoard(boardId)</span>
<span class="method-desc">حذف برد با تأیید کاربر</span>
</li>
<li>
<span class="method-name">switchBoard(boardId)</span>
<span class="method-desc">تغییر برد فعال</span>
</li>
<li>
<span class="method-name">renderBoardList()</span>
<span class="method-desc">رندر لیست بردها در منو</span>
</li>
</ul>

<h5 style="margin-top: 20px; color: var(--primary-light); font-size: 14px;">📑 ماژول ColumnManager</h5>
<ul class="method-list">
<li>
<span class="method-name">addColumn(boardId, title, bgColor, textColor)</span>
<span class="method-desc">افزودن ستون جدید به برد</span>
</li>
<li>
<span class="method-name">editColumn(columnId, data)</span>
<span class="method-desc">ویرایش ویژگی‌های ستون</span>
</li>
<li>
<span class="method-name">deleteColumn(columnId)</span>
<span class="method-desc">حذف ستون و کارت‌های آن</span>
</li>
<li>
<span class="method-name">reorderColumns(draggedId, targetId)</span>
<span class="method-desc">تغییر ترتیب ستون‌ها با Drag & Drop</span>
</li>
<li>
<span class="method-name">renderColumns(boardId)</span>
<span class="method-desc">رندر ستون‌های برد فعال</span>
</li>
</ul>

<h5 style="margin-top: 20px; color: var(--primary-light); font-size: 14px;">📝 ماژول CardManager</h5>
<ul class="method-list">
<li>
<span class="method-name">createCard(columnId, cardData)</span>
<span class="method-desc">ایجاد کارت جدید در ستون</span>
</li>
<li>
<span class="method-name">updateCard(cardId, updates)</span>
<span class="method-desc">به‌روزرسانی اطلاعات کارت</span>
</li>
<li>
<span class="method-name">deleteCard(cardId)</span>
<span class="method-desc">حذف کارت از ستون</span>
</li>
<li>
<span class="method-name">moveCard(cardId, fromColumnId, toColumnId)</span>
<span class="method-desc">جابجایی کارت بین ستون‌ها</span>
</li>
<li>
<span class="method-name">addChecklistItem(cardId, text)</span>
<span class="method-desc">افزودن آیتم به چک‌لیست</span>
</li>
<li>
<span class="method-name">toggleChecklistItem(cardId, itemId)</span>
<span class="method-desc">تغییر وضعیت آیتم چک‌لیست</span>
</li>
<li>
<span class="method-name">addDependency(cardId, dependOnCardId)</span>
<span class="method-desc">ایجاد وابستگی بین کارت‌ها</span>
</li>
<li>
<span class="method-name">assignMember(cardId, memberName)</span>
<span class="method-desc">تخصیص مسئول به کارت</span>
</li>
<li>
<span class="method-name">getCardDueStatus(card)</span>
<span class="method-desc">تعیین وضعیت سررسید کارت</span>
</li>
<li>
<span class="method-name">openCardModal(cardId)</span>
<span class="method-desc">باز کردن پنجره ویرایش کارت</span>
</li>
</ul>

<h5 style="margin-top: 20px; color: var(--primary-light); font-size: 14px;">🏷️ ماژول TagManager</h5>
<ul class="method-list">
<li>
<span class="method-name">createTag(name, color)</span>
<span class="method-desc">ایجاد تگ عمومی جدید</span>
</li>
<li>
<span class="method-name">deleteTag(tagId)</span>
<span class="method-desc">حذف تگ و حذف از کارت‌ها</span>
</li>
<li>
<span class="method-name">assignTagToCard(cardId, tagId)</span>
<span class="method-desc">تخصیص تگ به کارت</span>
</li>
<li>
<span class="method-name">removeTagFromCard(cardId, tagId)</span>
<span class="method-desc">حذف تگ از کارت</span>
</li>
<li>
<span class="method-name">renderTagManager()</span>
<span class="method-desc">رندر پنل مدیریت تگ‌ها</span>
</li>
</ul>

<h5 style="margin-top: 20px; color: var(--primary-light); font-size: 14px;">🔍 ماژول FilterEngine</h5>
<ul class="method-list">
<li>
<span class="method-name">applyFilters(criteria)</span>
<span class="method-desc">اعمال فیلترهای چندبعدی</span>
</li>
<li>
<span class="method-name">filterByTags(selectedTags)</span>
<span class="method-desc">فیلتر بر اساس تگ‌ها</span>
</li>
<li>
<span class="method-name">filterByAssignees(selectedAssignees)</span>
<span class="method-desc">فیلتر بر اساس مسئولین</span>
</li>
<li>
<span class="method-name">filterByDueStatus(status)</span>
<span class="method-desc">فیلتر بر اساس وضعیت سررسید</span>
</li>
<li>
<span class="method-name">searchCards(query)</span>
<span class="method-desc">جستجوی متنی در کارت‌ها</span>
</li>
<li>
<span class="method-name">clearAllFilters()</span>
<span class="method-desc">پاک‌سازی تمام فیلترها</span>
</li>
</ul>

<h5 style="margin-top: 20px; color: var(--primary-light); font-size: 14px;">📡 ماژول WebhookService</h5>
<ul class="method-list">
<li>
<span class="method-name">sendEvent(eventType, payload)</span>
<span class="method-desc">ارسال رویداد به URL پیکربندی‌شده</span>
</li>
<li>
<span class="method-name">testWebhook(url)</span>
<span class="method-desc">تست اتصال Webhook</span>
</li>
<li>
<span class="method-name">buildEventPayload(eventType, data)</span>
<span class="method-desc">ساخت ساختار JSON استاندارد رویداد</span>
</li>
</ul>

<h5 style="margin-top: 20px; color: var(--primary-light); font-size: 14px;">💾 ماژول PersistenceEngine</h5>
<ul class="method-list">
<li>
<span class="method-name">autoSave()</span>
<span class="method-desc">ذخیره خودکار در LocalStorage</span>
</li>
<li>
<span class="method-name">loadAutoSave()</span>
<span class="method-desc">بارگذاری داده‌های ذخیره‌شده خودکار</span>
</li>
<li>
<span class="method-name">scheduleAutoSave()</span>
<span class="method-desc">زمان‌بندی ذخیره با Debounce</span>
</li>
<li>
<span class="method-name">exportToJSON()</span>
<span class="method-desc">خروجی فایل JSON</span>
</li>
<li>
<span class="method-name">importFromJSON(file)</span>
<span class="method-desc">بارگذاری از فایل JSON</span>
</li>
<li>
<span class="method-name">exportToMarkdown(boardId)</span>
<span class="method-desc">خروجی فرمت Markdown</span>
</li>
<li>
<span class="method-name">exportToCSV(boardId)</span>
<span class="method-desc">خروجی فرمت CSV</span>
</li>
<li>
<span class="method-name">checkRecovery()</span>
<span class="method-desc">بررسی وجود داده‌های بازیابی</span>
</li>
</ul>

<h5 style="margin-top: 20px; color: var(--primary-light); font-size: 14px;">⌨️ ماژول ShortcutHandler</h5>
<ul class="method-list">
<li>
<span class="method-name">registerShortcuts()</span>
<span class="method-desc">ثبت تمام میانبرهای کیبورد</span>
</li>
<li>
<span class="method-name">handleKeyDown(event)</span>
<span class="method-desc">پردازش رویدادهای کیبورد</span>
</li>
<li>
<span class="method-name">navigateCards(direction)</span>
<span class="method-desc">پیمایش بین کارت‌ها با Tab</span>
</li>
<li>
<span class="method-name">moveCardWithKeyboard(direction)</span>
<span class="method-desc">جابجایی کارت با Alt+Arrow</span>
</li>
</ul>

<h4 style="margin-top: 30px; color: var(--primary); font-size: 16px;">د) الگوهای طراحی به‌کاررفته</h4>
<table class="styled-table">
<thead>
<tr>
<th>الگو</th>
<th>محل استفاده</th>
<th>هدف</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>State-Driven Rendering</strong></td>
<td>کل رابط کاربری</td>
<td>رندر UI بر اساس وضعیت مرکزی AppState</td>
</tr>
<tr>
<td><strong>Observer / Pub-Sub</strong></td>
<td>ارتباط ماژول‌ها</td>
<td>اطلاع‌رسانی تغییرات وضعیت به ماژول‌های وابسته</td>
</tr>
<tr>
<td><strong>Debouncing</strong></td>
<td>AutoSave Engine</td>
<td>جلوگیری از نوشتن مکرر در LocalStorage</td>
</tr>
<tr>
<td><strong>Factory</strong></td>
<td>ایجاد موجودیت‌ها</td>
<td>تولید اشیاء Card، Column، Board با UUID</td>
</tr>
<tr>
<td><strong>Command</strong></td>
<td>Undo/Redo</td>
<td>ذخیره عملیات برای امکان بازگشت</td>
</tr>
<tr>
<td><strong>Singleton</strong></td>
<td>AppState</td>
<td>یک منبع واحد حقیقت برای کل برنامه</td>
</tr>
</tbody>
</table>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">ه) جریان داده (Data Flow)</h4>
<div class="info-box">
<strong>🔄 چرخه حیات یک عملیات کاربر:</strong><br><br>
<strong>۱. User Action:</strong> کاربر یک کارت را از ستون «در حال انجام» به ستون «تکمیل‌شده» می‌کشد.<br>
<strong>۲. Event Capture:</strong> رویداد Drag & Drop توسط <code>ColumnManager</code> دریافت می‌شود.<br>
<strong>۳. State Update:</strong> متد <code>moveCard()</code> از <code>CardManager</code> فراخوانی شده و AppState به‌روزرسانی می‌شود.<br>
<strong>۴. History Log:</strong> متد <code>HistoryTracker.log()</code> جابجایی را با timestamp ثبت می‌کند.<br>
<strong>۵. Re-Render:</strong> متد <code>renderColumns()</code> رابط کاربری را با وضعیت جدید به‌روزرسانی می‌کند.<br>
<strong>۶. AutoSave:</strong> متد <code>scheduleAutoSave()</code> با تأخیر ۱ ثانیه، وضعیت را در LocalStorage ذخیره می‌کند.<br>
<strong>۷. Webhook:</strong> در صورت فعال بودن، متد <code>sendEvent('card.moved', payload)</code> رویداد را ارسال می‌کند.<br>
<strong>۸. Stats Update:</strong> متد <code>getProjectStats()</code> آمار نوار وضعیت را به‌روزرسانی می‌کند.
</div>

</div>
</div>

<!-- ═══════════════ بخش ۵: مخاطبان هدف ═══════════════ -->
<div class="section">
<div class="section-header">
<div class="section-number">۵</div>
<div class="section-title">
<h3>مناسب برای چه سازمان‌ها و تیم‌هایی؟</h3>
<div class="subtitle">Target Audience & Ideal Use Cases</div>
</div>
</div>
<div class="section-content">

<p>xKanban به‌گونه‌ای طراحی شده که طیف وسیعی از سازمان‌ها و تیم‌ها بتوانند از آن بهره‌مند شوند. با این حال، برخی محیط‌ها بیشترین ارزش را از این ابزار دریافت خواهند کرد:</p>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">الف) سازمان‌های هدف</h4>

<div class="audience-grid">
<div class="audience-card">
<div class="audience-icon">🏢</div>
<h5>استارتاپ‌ها و شرکت‌های کوچک</h5>
<p>تیم‌هایی که بودجه محدود دارند و به ابزاری رایگان، سریع و بدون سربار نیاز دارند. xKanban جایگزین ایده‌آل Trello و Asana برای این تیم‌هاست.</p>
</div>
<div class="audience-card">
<div class="audience-icon">🏛️</div>
<h5>سازمان‌های دولتی و نظامی</h5>
<p>محیط‌هایی با محدودیت‌های امنیتی شدید که استفاده از سرویس‌های ابری خارجی ممنوع است. xKanban با ذخیره‌سازی ۱۰۰٪ محلی، این الزامات را رعایت می‌کند.</p>
</div>
<div class="audience-card">
<div class="audience-icon">🏥</div>
<h5>مراکز بهداشتی و درمانی</h5>
<p>بیمارستان‌ها و مراکز تحقیقاتی که داده‌های حساس بیماران و پروژه‌ها نباید از شبکه داخلی خارج شود.</p>
</div>
<div class="audience-card">
<div class="audience-icon">🏦</div>
<h5>مؤسسات مالی و بانکی</h5>
<p>سازمان‌هایی که تحت مقررات سخت‌گیرانه حفاظت از داده (مانند PCI-DSS) فعالیت می‌کنند و نمی‌توانند اطلاعات پروژه‌ها را در سرورهای ثالث ذخیره کنند.</p>
</div>
<div class="audience-card">
<div class="audience-icon">🎓</div>
<h5>دانشگاه‌ها و مراکز آموزشی</h5>
<p>اساتید و دانشجویانی که برای مدیریت پروژه‌های تحقیقاتی، پایان‌نامه‌ها و کارهای گروهی به ابزاری ساده و رایگان نیاز دارند.</p>
</div>
<div class="audience-card">
<div class="audience-icon">🌍</div>
<h5>تیم‌های دورکار و فریلنسرها</h5>
<p>افرادی که در محیط‌های مختلف کار می‌کنند و نیاز دارند پروژه خود را روی فلش یا فضای ابری شخصی حمل کنند.</p>
</div>
</div>

<h4 style="margin-top: 30px; color: var(--primary); font-size: 16px;">ب) تیم‌های هدف</h4>
<table class="styled-table">
<thead>
<tr>
<th>نوع تیم</th>
<th>نحوه استفاده از xKanban</th>
<th>ارزش اصلی</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>تیم‌های توسعه نرم‌افزار</strong></td>
<td>مدیریت Sprint، باگ‌ها و Featureها</td>
<td>یکپارچگی با CI/CD از طریق Webhook</td>
</tr>
<tr>
<td><strong>تیم‌های DevOps</strong></td>
<td>ردیابی Incidentها و Change Requestها</td>
<td>اجرای آفلاین در محیط‌های ایزوله</td>
</tr>
<tr>
<td><strong>تیم‌های بازاریابی</strong></td>
<td>مدیریت کمپین‌ها و محتوای تولیدی</td>
<td>سادگی و سرعت راه‌اندازی</td>
</tr>
<tr>
<td><strong>تیم‌های منابع انسانی</strong></td>
<td>ردیابی فرآیند استخدام و Onboarding</td>
<td>حریم خصوصی داده‌های پرسنلی</td>
</tr>
<tr>
<td><strong>تیم‌های طراحی</strong></td>
<td>مدیریت پروژه‌های طراحی و بازخورد</td>
<td>رابط بصری جذاب و رنگ‌بندی سفارشی</td>
</tr>
<tr>
<td><strong>تیم‌های پشتیبانی</strong></td>
<td>مدیریت تیکت‌ها و درخواست‌های مشتریان</td>
<td>آمار بلادرنگ و فیلتر پیشرفته</td>
</tr>
<tr>
<td><strong>مدیران محصول</strong></td>
<td>نقشه راه محصول و اولویت‌بندی ویژگی‌ها</td>
<td>وابستگی بین کارت‌ها و چک‌لیست</td>
</tr>
</tbody>
</table>

<div class="highlight-box">
<strong>🎯 قانون کلی:</strong> هر تیمی که با سؤال «الان هر کس دارد روی چه چیزی کار می‌کند و وضعیتش چیست؟» مواجه است، یک کاندیدای ایده‌آل برای استفاده از xKanban محسوب می‌شود.
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">ج) چه زمانی xKanban بهترین انتخاب نیست؟</h4>
<div class="info-box">
<strong>💡 شفافیت کامل:</strong> xKanban برای سناریوهای زیر طراحی نشده و در این موارد، ابزارهای تخصصی‌تر توصیه می‌شوند:
<ul style="padding-right: 20px; margin-top: 10px;">
<li style="margin-bottom: 8px;">تیم‌های بسیار بزرگ (بیش از ۵۰ نفر) که نیاز به مدیریت دسترسی سطحی و نقش‌های پیچیده دارند</li>
<li style="margin-bottom: 8px;">سازمان‌هایی که نیاز به هم‌زمانی بلادرنگ (Real-time Collaboration) بین چندین کاربر دارند</li>
<li style="margin-bottom: 8px;">محیط‌هایی که نیاز به یکپارچگی عمیق با اکوسیستم‌های خاص (مانند SAP یا Salesforce) دارند</li>
</ul>
</div>

</div>
</div>

<!-- ═══════════════ بخش ۶: جمع‌بندی ═══════════════ -->
<div class="section">
<div class="section-header">
<div class="section-number">۶</div>
<div class="section-title">
<h3>جمع‌بندی و گام‌های بعدی</h3>
<div class="subtitle">Summary & Next Steps</div>
</div>
</div>
<div class="section-content">

<p>xKanban پاسخی عملی و مهندسی‌شده به یکی از رایج‌ترین چالش‌های تیم‌های مدرن است: مدیریت بصری کارها بدون پیچیدگی، بدون هزینه و بدون وابستگی. این ابزار از دل تجربه واقعی در مدیریت پروژه‌های نرم‌افزاری متولد شده و هر ویژگی آن، پاسخی مستقیم به یک نیاز واقعی است.</p>

<div class="conclusion-box">
<h3>🎯 چرا xKanban؟</h3>
<p>زیرا مدیریت پروژه نباید خودش یک پروژه باشد.</p>
<p>xKanban با ترکیب سادگی، قدرت و استقلال، ابزاری را ارائه می‌دهد که از لحظه اول قابل استفاده است، داده‌های شما را در اختیار خودتان نگه می‌دارد و هیچ هزینه‌ای بر دوش سازمان نمی‌گذارد.</p>
<div class="highlight">
<strong>💎 ارزش نهایی:</strong><br>
صفر هزینه + صفر وابستگی + صفر پیچیدگی = ۱۰۰٪ بهره‌وری
</div>
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">گام‌های پیشنهادی برای شروع:</h4>
<table class="styled-table">
<thead>
<tr>
<th>گام</th>
<th>اقدام</th>
<th>زمان تخمینی</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>۱</strong></td>
<td>فایل xKanban.html را در مرورگر باز کنید</td>
<td>۳ ثانیه</td>
</tr>
<tr>
<td><strong>۲</strong></td>
<td>یک پروژه و برد آزمایشی ایجاد کنید</td>
<td>۲ دقیقه</td>
</tr>
<tr>
<td><strong>۳</strong></td>
<td>ستون‌ها و کارت‌های واقعی تیم خود را وارد کنید</td>
<td>۱۰ دقیقه</td>
</tr>
<tr>
<td><strong>۴</strong></td>
<td>تگ‌ها و مسئولین را تعریف کنید</td>
<td>۵ دقیقه</td>
</tr>
<tr>
<td><strong>۵</strong></td>
<td>پروژه را به‌صورت فایل JSON ذخیره کنید</td>
<td>۱ دقیقه</td>
</tr>
<tr class="highlight-row">
<td><strong>۶</strong></td>
<td>از xKanban لذت ببرید! 🎉</td>
<td>همیشگی</td>
</tr>
</tbody>
</table>

<div class="info-box">
<strong>📞 تماس و پشتیبانی:</strong> برای دریافت مشاوره فنی، سفارشی‌سازی xKanban بر اساس نیازهای خاص سازمانی یا یکپارچگی با سیستم‌های موجود، آماده برگزاری جلسه فنی هستیم. طراحی، تحلیل و توسعه توسط <strong>هادی خزاعی اصل</strong>، معمار و توسعه‌دهنده ارشد نرم‌افزار.
</div>

</div>
</div>

<div class="page-info">
این مستند جهت معرفی محصول و تحلیل فنی تهیه شده است. هرگونه استفاده تجاری از محتوای این مستند بدون اجازه کتبی ممنوع است.
</div>

</div>

<!-- ═══════════════ FOOTER ═══════════════ -->
<footer class="document-footer">
<div class="footer-brand">طراحی، تحلیل و توسعه: هادی خزاعی اصل</div>
<div style="font-size: 11px; opacity: 0.8; margin-bottom: 5px;">معمار و توسعه‌دهنده ارشد نرم‌افزار | متخصص .NET و Angular</div>
<div class="footer-divider"></div>
<div>تاریخ تهیه: یکشنبه ۲۲ شهریور ۱۴۰۵ (۱۳ سپتامبر ۲۰۲۶) &nbsp;|&nbsp; ساعت: ۱۰:۳۰ &nbsp;|&nbsp; نسخه: ۱.۰.۰ &nbsp;|&nbsp; طبقه‌بندی: عمومی</div>
</footer>

</div>
</body>
</html>

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Resources\Tools\Document\xKanban_Techincal_Documentation.html

<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>مستند فنی پروژه xKanban | تحلیل جامع معماری و پیاده‌سازی</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap');
:root {
--primary: #1a365d;
--primary-light: #2c5282;
--accent: #c9a961;
--accent-dark: #a8893f;
--bg: #faf9f6;
--card-bg: #ffffff;
--text: #1a202c;
--text-muted: #4a5568;
--border: #e2e8f0;
--success: #38a169;
--danger: #e53e3e;
--warning: #dd6b20;
--shadow: 0 4px 6px rgba(0,0,0,0.05), 0 10px 30px rgba(26,54,93,0.08);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Vazirmatn', Tahoma, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.9;
font-size: 15px;
padding: 30px 15px;
}
.document-container {
max-width: 950px;
margin: 0 auto;
background: var(--card-bg);
box-shadow: var(--shadow);
border-radius: 12px;
overflow: hidden;
}
.document-header {
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);
color: white;
padding: 50px 40px 40px;
position: relative;
border-bottom: 4px solid var(--accent);
}
.document-header::before {
content: '';
position: absolute;
top: 0; right: 0;
width: 200px; height: 200px;
background: radial-gradient(circle, rgba(201,169,97,0.15) 0%, transparent 70%);
border-radius: 50%;
transform: translate(50%, -50%);
}
.header-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
position: relative;
z-index: 1;
}
.company-badge {
display: flex;
align-items: center;
gap: 12px;
}
.company-logo {
width: 60px; height: 60px;
background: var(--accent);
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
.doc-meta {
text-align: left;
font-size: 12px;
opacity: 0.9;
}
.doc-meta div { margin-bottom: 3px; }
.document-title {
text-align: center;
position: relative;
z-index: 1;
padding: 20px 0;
}
.document-title .label {
display: inline-block;
background: rgba(201,169,97,0.2);
color: var(--accent);
padding: 4px 16px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
margin-bottom: 15px;
border: 1px solid rgba(201,169,97,0.4);
}
.document-title h1 {
font-size: 28px;
font-weight: 800;
margin-bottom: 10px;
letter-spacing: -0.5px;
}
.document-title h2 {
font-size: 16px;
font-weight: 400;
opacity: 0.9;
}
.document-body {
padding: 40px;
}
.intro-text {
background: linear-gradient(to left, #f7fafc, #edf2f7);
border-right: 4px solid var(--accent);
padding: 20px 25px;
border-radius: 8px;
margin-bottom: 35px;
font-size: 14px;
color: var(--text-muted);
}
.section {
margin-bottom: 40px;
page-break-inside: avoid;
}
.section-header {
display: flex;
align-items: center;
gap: 15px;
padding-bottom: 12px;
border-bottom: 2px solid var(--border);
margin-bottom: 20px;
}
.section-number {
width: 50px; height: 50px;
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 800;
font-size: 20px;
flex-shrink: 0;
box-shadow: 0 4px 10px rgba(26,54,93,0.2);
}
.section-title { flex: 1; }
.section-title h3 {
font-size: 19px;
font-weight: 700;
color: var(--primary);
margin-bottom: 2px;
}
.section-title .subtitle {
font-size: 12px;
color: var(--text-muted);
}
.section-content {
padding-right: 10px;
}
.section-content p {
margin-bottom: 12px;
text-align: justify;
text-indent: 20px;
}
.section-content p.no-indent { text-indent: 0; }
.highlight-box {
background: linear-gradient(to left, #fffaf0, #fef5e7);
border: 1px solid #f6e05e;
border-right: 4px solid var(--accent);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.highlight-box strong {
color: var(--accent-dark);
}
.info-box {
background: linear-gradient(to left, #ebf8ff, #e6f6ff);
border: 1px solid #90cdf4;
border-right: 4px solid var(--primary-light);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.success-box {
background: linear-gradient(to left, #f0fff4, #e6fffa);
border: 1px solid #9ae6b4;
border-right: 4px solid var(--success);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.warning-box {
background: linear-gradient(to left, #fffaf0, #fef5e7);
border: 1px solid #fbd38d;
border-right: 4px solid var(--warning);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.code-box {
background: #1a202c;
color: #e2e8f0;
border-right: 4px solid var(--accent);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 13px;
font-family: 'Consolas', 'Courier New', monospace;
direction: ltr;
text-align: left;
overflow-x: auto;
line-height: 1.7;
}
.code-box .key { color: #c9a961; }
.code-box .str { color: #68d391; }
.code-box .num { color: #63b3ed; }
.code-box .comment { color: #718096; font-style: italic; }
.styled-table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
}
.styled-table thead {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
}
.styled-table th {
padding: 14px 16px;
text-align: right;
font-weight: 600;
font-size: 13px;
}
.styled-table td {
padding: 13px 16px;
border-bottom: 1px solid var(--border);
font-size: 13px;
}
.styled-table tbody tr:last-child td { border-bottom: none; }
.styled-table tbody tr:nth-child(even) { background: #f8fafc; }
.styled-table .highlight-row {
background: #fffaf0 !important;
font-weight: 600;
}
.comparison-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin: 25px 0;
}
.comparison-card {
background: #f8fafc;
border: 2px solid var(--border);
border-radius: 10px;
padding: 20px;
position: relative;
}
.comparison-card.recommended {
border-color: var(--success);
background: linear-gradient(to left, #f0fff4, #e6fffa);
}
.comparison-card .card-badge {
position: absolute;
top: -10px;
right: 20px;
background: var(--success);
color: white;
padding: 3px 12px;
border-radius: 15px;
font-size: 11px;
font-weight: 600;
}
.comparison-card h4 {
font-size: 16px;
font-weight: 700;
color: var(--primary);
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 2px solid var(--border);
}
.comparison-card.recommended h4 {
border-bottom-color: var(--success);
}
.comparison-card ul {
list-style: none;
padding: 0;
}
.comparison-card ul li {
padding: 8px 0;
border-bottom: 1px solid var(--border);
font-size: 13px;
display: flex;
justify-content: space-between;
}
.comparison-card ul li:last-child { border-bottom: none; }
.comparison-card ul li .value {
font-weight: 600;
color: var(--primary);
}
.quote-box {
background: linear-gradient(135deg, #f7fafc, #edf2f7);
border-right: 4px solid var(--accent);
padding: 20px 25px;
border-radius: 8px;
margin: 20px 0;
position: relative;
}
.quote-box::before {
content: '"';
position: absolute;
top: -10px;
right: 15px;
font-size: 60px;
color: var(--accent);
opacity: 0.3;
font-family: Georgia, serif;
}
.quote-box p {
font-style: italic;
color: var(--text-muted);
margin-bottom: 10px;
text-indent: 0 !important;
}
.quote-box .source {
font-size: 12px;
color: var(--text-muted);
text-align: left;
font-style: normal;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
margin: 25px 0;
}
.stat-card {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
padding: 20px;
border-radius: 10px;
text-align: center;
box-shadow: 0 4px 12px rgba(26,54,93,0.2);
}
.stat-card .stat-value {
font-size: 24px;
font-weight: 800;
margin-bottom: 5px;
color: var(--accent);
line-height: 1.3;
}
.stat-card .stat-label {
font-size: 12px;
opacity: 0.9;
}
.feature-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
margin: 25px 0;
}
.feature-card {
background: #f8fafc;
border: 1px solid var(--border);
border-right: 4px solid var(--primary-light);
padding: 18px;
border-radius: 8px;
}
.feature-card .feature-icon {
font-size: 24px;
margin-bottom: 10px;
}
.feature-card h5 {
font-size: 15px;
font-weight: 700;
color: var(--primary);
margin-bottom: 8px;
}
.feature-card p {
font-size: 13px;
color: var(--text-muted);
text-indent: 0 !important;
margin-bottom: 0 !important;
}
.architecture-diagram {
background: linear-gradient(to left, #f7fafc, #edf2f7);
border: 2px solid var(--border);
border-radius: 10px;
padding: 25px;
margin: 25px 0;
text-align: center;
}
.arch-layer {
background: white;
border: 2px solid var(--primary-light);
border-radius: 8px;
padding: 15px;
margin: 10px auto;
max-width: 700px;
box-shadow: 0 2px 6px rgba(0,0,0,0.05);
}
.arch-layer-title {
font-weight: 700;
color: var(--primary);
margin-bottom: 10px;
font-size: 14px;
border-bottom: 2px solid var(--accent);
padding-bottom: 5px;
display: inline-block;
}
.arch-components {
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: center;
}
.arch-chip {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
padding: 6px 14px;
border-radius: 15px;
font-size: 12px;
font-weight: 500;
}
.arch-arrow {
text-align: center;
font-size: 24px;
color: var(--accent);
margin: 5px 0;
}
.conclusion-box {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
padding: 30px;
border-radius: 12px;
margin: 30px 0;
text-align: center;
box-shadow: 0 8px 20px rgba(26,54,93,0.3);
}
.conclusion-box h3 {
font-size: 22px;
font-weight: 800;
margin-bottom: 15px;
color: var(--accent);
}
.conclusion-box p {
font-size: 14px;
line-height: 1.8;
text-indent: 0 !important;
margin-bottom: 15px;
}
.conclusion-box .highlight {
background: rgba(201,169,97,0.2);
padding: 15px 20px;
border-radius: 8px;
margin-top: 20px;
border: 1px solid rgba(201,169,97,0.4);
}
.document-footer {
background: var(--primary);
color: white;
padding: 25px 40px;
text-align: center;
font-size: 12px;
margin-top: 40px;
}
.document-footer .footer-brand {
font-weight: 700;
color: var(--accent);
margin-bottom: 5px;
font-size: 14px;
}
.footer-divider {
width: 60px;
height: 2px;
background: var(--accent);
margin: 10px auto;
}
.page-info {
text-align: center;
padding: 15px;
font-size: 11px;
color: var(--text-muted);
border-top: 1px solid var(--border);
margin-top: 30px;
}
@media print {
body { background: white; padding: 0; }
.document-container { box-shadow: none; border-radius: 0; }
.section { page-break-inside: avoid; }
}
@media (max-width: 768px) {
.document-body { padding: 25px 20px; }
.document-header { padding: 30px 20px; }
.comparison-grid { grid-template-columns: 1fr; }
.stats-grid { grid-template-columns: 1fr; }
.feature-grid { grid-template-columns: 1fr; }
.header-top { flex-direction: column; gap: 15px; }
.doc-meta { text-align: center; }
}
</style>
</head>
<body>
<div class="document-container">
<header class="document-header">
<div class="header-top">
<div class="company-badge">
<div class="company-logo"></div>
</div>
<div class="doc-meta">
<div>شماره مستند: TECH-XKN-1405-001</div>
<div>نسخه: ۱.۰.۰</div>
<div>تاریخ تهیه: ۲۲ شهریور ۱۴۰۵ (۱۳ سپتامبر ۲۰۲۶)</div>
<div>ساعت: ۱۰:۰۰</div>
<div>طبقه‌بندی: داخلی - فنی</div>
</div>
</div>
<div class="document-title">
<span class="label">TECHNICAL DOCUMENTATION</span>
<h1>مستند فنی پروژه xKanban</h1>
<h2>تحلیل جامع معماری، ساختار داده و پیاده‌سازی ابزار مدیریت بردهای کانبان</h2>
</div>
</header>

<div class="document-body">
<div class="intro-text">
<strong>مخاطبان مستند:</strong> تیم فنی، معماران نرم‌افزار، توسعه‌دهندگان و مدیران فنی<br>
این مستند به‌عنوان مرجع فنی جامع پروژه <strong>xKanban</strong> تهیه شده است و تمامی لایه‌های معماری، ساختار داده‌ها، ماژول‌های کلیدی و مکانیزم‌های پیاده‌سازی را از دیدگاه یک معمار ارشد نرم‌افزار تشریح می‌نماید. هدف از این مستند، ارائه درک عمیق از تصمیمات طراحی و ایجاد پایه‌ای مستحکم برای توسعه و نگهداری آتی محصول می‌باشد.
</div>

<!-- بخش ۱ -->
<div class="section">
<div class="section-header">
<div class="section-number">۱</div>
<div class="section-title">
<h3>خلاصه اجرایی و معرفی محصول</h3>
<div class="subtitle">Executive Summary & Product Overview</div>
</div>
</div>
<div class="section-content">
<p>پروژه <strong>xKanban</strong> یک ابزار تحت وب مستقل (Standalone) برای مدیریت بردهای کانبان است که با هدف ارائه راهکاری چابک، قابل حمل و بدون نیاز به سرور طراحی و پیاده‌سازی شده است. این محصول به کاربران امکان می‌دهد تا پروژه‌ها، بردها، ستون‌ها و کارت‌های کاری خود را با ساختاری استاندارد و حرفه‌ای مدیریت نمایند.</p>

<div class="stats-grid">
<div class="stat-card">
<div class="stat-value">۱۰۰٪<br>Client-Side</div>
<div class="stat-label">اجرا در مرورگر بدون نیاز به سرور</div>
</div>
<div class="stat-card">
<div class="stat-value">Single<br>File</div>
<div class="stat-label">توزیع به صورت یک فایل HTML مستقل</div>
</div>
<div class="stat-card">
<div class="stat-value">Zero<br>Dependency</div>
<div class="stat-label">بدون وابستگی به کتابخانه خارجی</div>
</div>
</div>

<div class="highlight-box">
<strong>🎯 چشم‌انداز طراحی:</strong> xKanban با فلسفه «ابزار در جیب کاربر» طراحی شده است؛ به گونه‌ای که کاربر بدون نیاز به اتصال اینترنت، نصب نرم‌افزار یا ایجاد حساب کاربری، به یک سیستم کامل مدیریت پروژه دسترسی داشته باشد.
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">اهداف کلیدی پروژه:</h4>
<ul style="padding-right: 20px; margin-top: 10px;">
<li style="margin-bottom: 8px;"><strong>استقلال کامل:</strong> اجرا به صورت آفلاین و بدون نیاز به زیرساخت سرور</li>
<li style="margin-bottom: 8px;"><strong>قابلیت حمل:</strong> ذخیره و انتقال پروژه از طریق فایل JSON</li>
<li style="margin-bottom: 8px;"><strong>تجربه کاربری حرفه‌ای:</strong> رابط کاربری مدرن با پشتیبانی از حالت تاریک/روشن</li>
<li style="margin-bottom: 8px;"><strong>استانداردسازی:</strong> پیاده‌سازی دقیق متدولوژی کانبان با قابلیت‌های پیشرفته</li>
<li style="margin-bottom: 8px;"><strong>یکپارچگی:</strong> اتصال به سیستم‌های خارجی از طریق Webhook</li>
</ul>
</div>
</div>

<!-- بخش ۲ -->
<div class="section">
<div class="section-header">
<div class="section-number">۲</div>
<div class="section-title">
<h3>معماری کلی سیستم</h3>
<div class="subtitle">System Architecture Overview</div>
</div>
</div>
<div class="section-content">
<p>معماری xKanban بر پایه الگوی <strong>Single-Page Application (SPA)</strong> با رویکرد <strong>State-Driven</strong> طراحی شده است. تمامی منطق برنامه در لایه کلاینت اجرا می‌شود و از الگوی <strong>MVC سبک</strong> برای جداسازی مسئولیت‌ها استفاده شده است.</p>

<div class="architecture-diagram">
<div class="arch-layer">
<div class="arch-layer-title">🎨 Presentation Layer (UI)</div>
<div class="arch-components">
<span class="arch-chip">Toolbar</span>
<span class="arch-chip">Board View</span>
<span class="arch-chip">Column Renderer</span>
<span class="arch-chip">Card Renderer</span>
<span class="arch-chip">Modal System</span>
<span class="arch-chip">Filter Panel</span>
</div>
</div>
<div class="arch-arrow">▼</div>
<div class="arch-layer">
<div class="arch-layer-title">⚙️ Application Layer (Logic)</div>
<div class="arch-components">
<span class="arch-chip">Board Manager</span>
<span class="arch-chip">Column Manager</span>
<span class="arch-chip">Card Manager</span>
<span class="arch-chip">Tag Manager</span>
<span class="arch-chip">Filter Engine</span>
<span class="arch-chip">Shortcut Handler</span>
<span class="arch-chip">Webhook Service</span>
</div>
</div>
<div class="arch-arrow">▼</div>
<div class="arch-layer">
<div class="arch-layer-title">💾 Data Layer (State & Persistence)</div>
<div class="arch-components">
<span class="arch-chip">Project State</span>
<span class="arch-chip">LocalStorage</span>
<span class="arch-chip">AutoSave Engine</span>
<span class="arch-chip">JSON Export/Import</span>
<span class="arch-chip">History Tracker</span>
</div>
</div>
</div>

<table class="styled-table">
<thead>
<tr>
<th>لایه معماری</th>
<th>مسئولیت اصلی</th>
<th>تکنولوژی</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Presentation</strong></td>
<td>رندر رابط کاربری و مدیریت رویدادها</td>
<td>HTML5 + CSS3 + Vanilla JS</td>
</tr>
<tr>
<td><strong>Application</strong></td>
<td>منطق تجاری و مدیریت عملیات</td>
<td>ES6+ Modules Pattern</td>
</tr>
<tr>
<td><strong>Data</strong></td>
<td>مدیریت وضعیت و پایداری داده</td>
<td>LocalStorage + JSON API</td>
</tr>
<tr class="highlight-row">
<td><strong>Integration</strong></td>
<td>ارتباط با سیستم‌های خارجی</td>
<td>Fetch API + Webhook</td>
</tr>
</tbody>
</table>

<div class="info-box">
<strong>💡 نکته معماری:</strong> انتخاب معماری تک‌فایلی (Single-File) با وجود پیچیدگی بالای پروژه، یک تصمیم استراتژیک بوده است. این رویکرد امکان توزیع آسان، اجرای آفلاین و عدم وابستگی به زیرساخت را فراهم می‌آورد.
</div>
</div>
</div>

<!-- بخش ۳ -->
<div class="section">
<div class="section-header">
<div class="section-number">۳</div>
<div class="section-title">
<h3>ساختار داده‌ها و مدل‌ها</h3>
<div class="subtitle">Data Models & Schema Design</div>
</div>
</div>
<div class="section-content">
<p>مدل داده‌ای xKanban بر پایه ساختار سلسله‌مراتبی <strong>Project → Board → Column → Card</strong> طراحی شده است. هر سطح از این سلسله‌مراتب، موجودیت مستقلی با ویژگی‌ها و روابط خاص خود دارد.</p>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">الف) مدل Project (پروژه)</h4>
<div class="code-box">
<span class="key">Project</span> {<br>
&nbsp;&nbsp;<span class="key">id</span>: <span class="str">"uuid"</span>,<br>
&nbsp;&nbsp;<span class="key">title</span>: <span class="str">"string"</span>,<br>
&nbsp;&nbsp;<span class="key">description</span>: <span class="str">"string"</span>,<br>
&nbsp;&nbsp;<span class="key">createdAt</span>: <span class="str">"ISO8601"</span>,<br>
&nbsp;&nbsp;<span class="key">webhook</span>: { <span class="key">enabled</span>: <span class="num">boolean</span>, <span class="key">url</span>: <span class="str">"string"</span> },<br>
&nbsp;&nbsp;<span class="key">boards</span>: [<span class="key">Board</span>],<br>
&nbsp;&nbsp;<span class="key">globalTags</span>: [<span class="key">Tag</span>]<br>
}
</div>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">ب) مدل Board (برد)</h4>
<div class="code-box">
<span class="key">Board</span> {<br>
&nbsp;&nbsp;<span class="key">id</span>: <span class="str">"uuid"</span>,<br>
&nbsp;&nbsp;<span class="key">title</span>: <span class="str">"string"</span>,<br>
&nbsp;&nbsp;<span class="key">description</span>: <span class="str">"string"</span>,<br>
&nbsp;&nbsp;<span class="key">backgroundColor</span>: <span class="str">"#hex"</span>,<br>
&nbsp;&nbsp;<span class="key">columns</span>: [<span class="key">Column</span>]<br>
}
</div>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">ج) مدل Column (ستون)</h4>
<div class="code-box">
<span class="key">Column</span> {<br>
&nbsp;&nbsp;<span class="key">id</span>: <span class="str">"uuid"</span>,<br>
&nbsp;&nbsp;<span class="key">title</span>: <span class="str">"string"</span>,<br>
&nbsp;&nbsp;<span class="key">backgroundColor</span>: <span class="str">"#hex"</span>,<br>
&nbsp;&nbsp;<span class="key">textColor</span>: <span class="str">"#hex"</span>,<br>
&nbsp;&nbsp;<span class="key">order</span>: <span class="num">number</span>,<br>
&nbsp;&nbsp;<span class="key">cards</span>: [<span class="key">Card</span>]<br>
}
</div>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">د) مدل Card (کارت) - غنی‌ترین موجودیت</h4>
<div class="code-box">
<span class="key">Card</span> {<br>
&nbsp;&nbsp;<span class="key">id</span>: <span class="str">"uuid"</span>,<br>
&nbsp;&nbsp;<span class="key">title</span>: <span class="str">"string"</span>,<br>
&nbsp;&nbsp;<span class="key">description</span>: <span class="str">"string"</span>,<br>
&nbsp;&nbsp;<span class="key">dueDate</span>: <span class="str">"ISO8601 | null"</span>,<br>
&nbsp;&nbsp;<span class="key">backgroundColor</span>: <span class="str">"#hex"</span>,<br>
&nbsp;&nbsp;<span class="key">assignees</span>: [<span class="str">"string"</span>],<br>
&nbsp;&nbsp;<span class="key">tags</span>: [<span class="str">"tagId"</span>],<br>
&nbsp;&nbsp;<span class="key">dependencies</span>: [<span class="str">"cardId"</span>],<br>
&nbsp;&nbsp;<span class="key">checklist</span>: [{ <span class="key">text</span>: <span class="str">"string"</span>, <span class="key">done</span>: <span class="num">boolean</span> }],<br>
&nbsp;&nbsp;<span class="key">history</span>: [{ <span class="key">timestamp</span>, <span class="key">action</span>, <span class="key">fromColumn</span>, <span class="key">toColumn</span> }]<br>
}
</div>

<div class="warning-box">
<strong>⚠️ نکته طراحی مهم:</strong> استفاده از UUID برای شناسه‌ها، امکان ادغام پروژه‌ها، جلوگیری از تداخل شناسه‌ها در عملیات Import/Export و پشتیبانی از همزمانی (Concurrency) را فراهم می‌آورد. این تصمیم برای مقیاس‌پذیری آینده حیاتی است.
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">روابط بین موجودیت‌ها:</h4>
<table class="styled-table">
<thead>
<tr>
<th>رابطه</th>
<th>نوع</th>
<th>توضیح</th>
</tr>
</thead>
<tbody>
<tr>
<td>Project → Boards</td>
<td>One-to-Many</td>
<td>هر پروژه می‌تواند چندین برد داشته باشد</td>
</tr>
<tr>
<td>Board → Columns</td>
<td>One-to-Many</td>
<td>هر برد شامل ستون‌های متعدد است</td>
</tr>
<tr>
<td>Column → Cards</td>
<td>One-to-Many</td>
<td>هر ستون شامل کارت‌های متعدد است</td>
</tr>
<tr>
<td>Card ↔ Card</td>
<td>Many-to-Many</td>
<td>وابستگی بین کارت‌ها (Dependencies)</td>
</tr>
<tr>
<td>Card → Tags</td>
<td>Many-to-Many</td>
<td>کارت‌ها می‌توانند چندین برچسب داشته باشند</td>
</tr>
<tr>
<td>Card → Assignees</td>
<td>Many-to-Many</td>
<td>هر کارت می‌تواند چندین مسئول داشته باشد</td>
</tr>
</tbody>
</table>
</div>
</div>

<!-- بخش ۴ -->
<div class="section">
<div class="section-header">
<div class="section-number">۴</div>
<div class="section-title">
<h3>ماژول‌های کلیدی و مسئولیت‌ها</h3>
<div class="subtitle">Core Modules & Responsibilities</div>
</div>
</div>
<div class="section-content">
<p>سیستم xKanban از ماژول‌های مجزا و با مسئولیت مشخص تشکیل شده است. این جداسازی بر اساس اصل <strong>Single Responsibility Principle (SRP)</strong> انجام شده و امکان نگهداری و توسعه آتی را فراهم می‌آورد.</p>

<div class="feature-grid">
<div class="feature-card">
<div class="feature-icon">📊</div>
<h5>Board Manager</h5>
<p>مدیریت چرخه حیات بردها شامل ایجاد، ویرایش، حذف و جابجایی بین بردها. نگهداری وضعیت فعال برد فعلی.</p>
</div>
<div class="feature-card">
<div class="feature-icon">📑</div>
<h5>Column Manager</h5>
<p>مدیریت ستون‌ها با قابلیت Drag & Drop برای تغییر ترتیب. پشتیبانی از رنگ‌بندی مستقل برای هر ستون.</p>
</div>
<div class="feature-card">
<div class="feature-icon">📝</div>
<h5>Card Manager</h5>
<p>مدیریت کامل کارت‌ها شامل متادیتای غنی، چک‌لیست، وابستگی‌ها و تاریخچه جابجایی.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🏷️</div>
<h5>Tag Manager</h5>
<p>مدیریت تگ‌های عمومی پروژه با قابلیت استفاده مجدد در کارت‌های مختلف.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🔍</div>
<h5>Filter Engine</h5>
<p>موتور فیلتر چندبعدی بر اساس تگ، مسئول، وضعیت و تاریخ سررسید.</p>
</div>
<div class="feature-card">
<div class="feature-icon">⌨️</div>
<h5>Shortcut Handler</h5>
<p>مدیریت میانبرهای کیبورد برای افزایش بهره‌وری کاربران حرفه‌ای.</p>
</div>
<div class="feature-card">
<div class="feature-icon">💾</div>
<h5>AutoSave Engine</h5>
<p>ذخیره خودکار وضعیت در LocalStorage با مکانیزم بازیابی در صورت قطع ناگهانی.</p>
</div>
<div class="feature-card">
<div class="feature-icon">📡</div>
<h5>Webhook Service</h5>
<p>ارسال رویدادهای پروژه به آدرس URL پیکربندی‌شده با ساختار JSON استاندارد.</p>
</div>
</div>

<div class="success-box">
<strong>✓ مزایای معماری ماژولار:</strong>
<ul style="padding-right: 20px; margin-top: 10px;">
<li>جداسازی کامل مسئولیت‌ها و کاهش Coupling</li>
<li>قابلیت تست‌پذیری هر ماژول به صورت مستقل</li>
<li>امکان توسعه ویژگی‌های جدید بدون تأثیر بر ماژول‌های موجود</li>
<li>کاهش پیچیدگی شناختی برای توسعه‌دهندگان جدید</li>
</ul>
</div>
</div>
</div>

<!-- بخش ۵ -->
<div class="section">
<div class="section-header">
<div class="section-number">۵</div>
<div class="section-title">
<h3>ویژگی‌های کلیدی و قابلیت‌ها</h3>
<div class="subtitle">Key Features & Capabilities</div>
</div>
</div>
<div class="section-content">
<p>xKanban مجموعه‌ای از ویژگی‌های پیشرفته را ارائه می‌دهد که آن را از ابزارهای ساده کانبان متمایز می‌سازد. در ادامه به تحلیل فنی هر ویژگی پرداخته شده است:</p>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">۱) سیستم مدیریت بردهای چندگانه</h4>
<p class="no-indent">امکان ایجاد و مدیریت چندین برد مستقل در یک پروژه، با قابلیت جابجایی سریع بین آن‌ها از طریق منوی کشویی. هر برد دارای تنظیمات مستقل شامل رنگ پس‌زمینه و توضیحات اختصاصی است.</p>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">۲) Drag & Drop پیشرفته ستون‌ها</h4>
<p class="no-indent">پیاده‌سازی مکانیزم Drag & Drop با استفاده از HTML5 Drag API که امکان تغییر ترتیب ستون‌ها را با کشیدن هدر آن‌ها فراهم می‌آورد. این ویژگی با حفظ وضعیت در مدل داده، پایداری ترتیب را تضمین می‌کند.</p>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">۳) کارت‌های غنی با متادیتای کامل</h4>
<p class="no-indent">هر کارت می‌تواند شامل اطلاعات زیر باشد:</p>
<ul style="padding-right: 20px; margin-top: 10px;">
<li style="margin-bottom: 8px;"><strong>تاریخ سررسید:</strong> با قابلیت نمایش وضعیت‌های مختلف (عقب‌افتاده، نزدیک به موعد، عادی، بدون تاریخ)</li>
<li style="margin-bottom: 8px;"><strong>مسئولین متعدد:</strong> امکان تخصیص چندین نفر به یک کارت</li>
<li style="margin-bottom: 8px;"><strong>برچسب‌های رنگی:</strong> استفاده از تگ‌های عمومی پروژه</li>
<li style="margin-bottom: 8px;"><strong>وابستگی‌ها:</strong> ایجاد روابط Dependency بین کارت‌ها</li>
<li style="margin-bottom: 8px;"><strong>چک‌لیست:</strong> ریزوظایف با قابلیت علامت‌گذاری</li>
<li style="margin-bottom: 8px;"><strong>تاریخچه:</strong> ثبت کامل جابجایی‌های کارت بین ستون‌ها</li>
</ul>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">۴) موتور فیلتر چندبعدی</h4>
<p class="no-indent">سیستم فیلتر پیشرفته با قابلیت ترکیب فیلترهای مختلف:</p>
<div class="code-box">
<span class="comment">// نمونه‌ای از منطق فیلتر</span><br>
<span class="key">filteredCards</span> = cards<br>
&nbsp;&nbsp;.<span class="key">filter</span>(c => selectedTags.length === 0 || <span class="key">c.tags.some</span>(t => selectedTags.<span class="key">includes</span>(t)))<br>
&nbsp;&nbsp;.<span class="key">filter</span>(c => selectedAssignees.length === 0 || <span class="key">c.assignees.some</span>(a => selectedAssignees.<span class="key">includes</span>(a)))<br>
&nbsp;&nbsp;.<span class="key">filter</span>(c => <span class="key">matchStatus</span>(c, selectedStatus))<br>
&nbsp;&nbsp;.<span class="key">filter</span>(c => <span class="key">matchSearch</span>(c, searchText));
</div>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">۵) سیستم Webhook یکپارچه</h4>
<p class="no-indent">امکان ارسال رویدادهای پروژه به سیستم‌های خارجی (مانند Slack، Teams یا سرویس‌های سفارشی) با ساختار استاندارد JSON:</p>
<div class="code-box">
{<br>
&nbsp;&nbsp;<span class="key">"eventId"</span>: <span class="str">"uuid-v4"</span>,<br>
&nbsp;&nbsp;<span class="key">"timestamp"</span>: <span class="str">"2026-09-13T10:00:00Z"</span>,<br>
&nbsp;&nbsp;<span class="key">"eventType"</span>: <span class="str">"card.moved"</span>,<br>
&nbsp;&nbsp;<span class="key">"project"</span>: { <span class="key">"id"</span>: <span class="str">"..."</span>, <span class="key">"title"</span>: <span class="str">"..."</span> },<br>
&nbsp;&nbsp;<span class="key">"board"</span>: { <span class="key">"id"</span>: <span class="str">"..."</span>, <span class="key">"title"</span>: <span class="str">"..."</span> },<br>
&nbsp;&nbsp;<span class="key">"data"</span>: { <span class="key">"cardId"</span>: <span class="str">"..."</span>, <span class="key">"from"</span>: <span class="str">"..."</span>, <span class="key">"to"</span>: <span class="str">"..."</span> }<br>
}
</div>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">۶) میانبرهای کیبورد جامع</h4>
<table class="styled-table">
<thead>
<tr>
<th>میانبر</th>
<th>عملکرد</th>
<th>کاربرد</th>
</tr>
</thead>
<tbody>
<tr><td><code>Ctrl+S</code></td><td>ذخیره سریع</td><td>ذخیره فوری پروژه</td></tr>
<tr><td><code>Ctrl+Z</code></td><td>بازگشت (Undo)</td><td>لغو آخرین عملیات</td></tr>
<tr><td><code>Ctrl+Y</code></td><td>از نو (Redo)</td><td>اعمال مجدد عملیات لغوشده</td></tr>
<tr><td><code>Ctrl+F</code></td><td>جستجو</td><td>باز کردن پنل جستجو</td></tr>
<tr><td><code>Esc</code></td><td>بستن پنجره‌ها</td><td>بستن Modal های باز</td></tr>
<tr><td><code>Tab</code></td><td>انتخاب کارت</td><td>پیمایش بین کارت‌ها</td></tr>
<tr><td><code>Alt+←/→</code></td><td>جابجایی کارت</td><td>انتقال کارت بین ستون‌ها</td></tr>
<tr><td><code>Enter</code></td><td>ویرایش کارت</td><td>باز کردن Modal ویرایش</td></tr>
</tbody>
</table>
</div>
</div>

<!-- بخش ۶ -->
<div class="section">
<div class="section-header">
<div class="section-number">۶</div>
<div class="section-title">
<h3>مکانیزم‌های ذخیره‌سازی و پایداری</h3>
<div class="subtitle">Persistence & Storage Mechanisms</div>
</div>
</div>
<div class="section-content">
<p>سیستم xKanban از چندین لایه ذخیره‌سازی برای تضمین پایداری داده‌ها استفاده می‌کند. این رویکرد چندلایه، از دست رفتن داده‌ها را در سناریوهای مختلف به حداقل می‌رساند.</p>

<div class="comparison-grid">
<div class="comparison-card">
<h4>💾 ذخیره‌سازی محلی (LocalStorage)</h4>
<ul>
<li><span>نوع</span> <span class="value">خودکار و شفاف</span></li>
<li><span>فرکانس</span> <span class="value">پس از هر تغییر</span></li>
<li><span>حجم</span> <span class="value">تا ۵ مگابایت</span></li>
<li><span>دوام</span> <span class="value">دائمی در مرورگر</span></li>
</ul>
</div>
<div class="comparison-card recommended">
<span class="card-badge">پیشنهادی ✓</span>
<h4>📁 خروجی فایل (JSON)</h4>
<ul>
<li><span>نوع</span> <span class="value">دستی و کنترل‌شده</span></li>
<li><span>فرکانس</span> <span class="value">بر اساس نیاز کاربر</span></li>
<li><span>حجم</span> <span class="value">نامحدود</span></li>
<li><span>دوام</span> <span class="value">دائمی و قابل انتقال</span></li>
</ul>
</div>
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">مکانیزم AutoSave و بازیابی:</h4>
<p class="no-indent">سیستم AutoSave با استفاده از الگوی <strong>Debouncing</strong> پیاده‌سازی شده است تا از نوشتن مکرر و غیرضروری در LocalStorage جلوگیری شود. در زمان راه‌اندازی، سیستم به صورت خودکار داده‌های ذخیره‌شده را بررسی کرده و در صورت وجود داده‌های بازیابی‌نشده، به کاربر اطلاع می‌دهد.</p>

<div class="code-box">
<span class="comment">// الگوی Debounced AutoSave</span><br>
<span class="key">let</span> saveTimeout;<br>
<span class="key">function</span> <span class="key">scheduleAutoSave</span>() {<br>
&nbsp;&nbsp;<span class="key">clearTimeout</span>(saveTimeout);<br>
&nbsp;&nbsp;saveTimeout = <span class="key">setTimeout</span>(() => {<br>
&nbsp;&nbsp;&nbsp;&nbsp;localStorage.<span class="key">setItem</span>(<span class="str">'xkanban_autosave'</span>, <span class="key">JSON.stringify</span>(state));<br>
&nbsp;&nbsp;}, <span class="num">1000</span>); <span class="comment">// تأخیر ۱ ثانیه‌ای</span><br>
}
</div>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">فرمت‌های خروجی پشتیبانی‌شده:</h4>
<table class="styled-table">
<thead>
<tr>
<th>فرمت</th>
<th>کاربرد</th>
<th>ویژگی‌ها</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>JSON</strong></td>
<td>پشتیبان‌گیری کامل و انتقال پروژه</td>
<td>حفظ تمامی داده‌ها و ساختار</td>
</tr>
<tr>
<td><strong>Markdown (MD)</strong></td>
<td>مستندسازی و اشتراک‌گذاری</td>
<td>خروجی خوانا و قابل ویرایش</td>
</tr>
<tr>
<td><strong>CSV</strong></td>
<td>تحلیل داده و وارد کردن به Excel</td>
<td>ساختار جدولی استاندارد</td>
</tr>
</tbody>
</table>

<div class="info-box">
<strong>💡 نکته فنی:</strong> انتخاب JSON به عنوان فرمت اصلی ذخیره‌سازی، امکان تعامل با سایر سیستم‌ها، پشتیبانی از نسخه‌بندی داده‌ها و سهولت در دیباگ را فراهم می‌آورد. همچنین ساختار درختی JSON با مدل داده‌ای پروژه همخوانی کامل دارد.
</div>
</div>
</div>

<!-- بخش ۷ -->
<div class="section">
<div class="section-header">
<div class="section-number">۷</div>
<div class="section-title">
<h3>رابط کاربری و تجربه کاربری</h3>
<div class="subtitle">UI/UX Design Principles</div>
</div>
</div>
<div class="section-content">
<p>طراحی رابط کاربری xKanban بر اساس اصول <strong>Modern UI/UX</strong> و با تمرکز بر بهره‌وری کاربر انجام شده است. در ادامه به ویژگی‌های کلیدی طراحی پرداخته شده است:</p>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">الف) طراحی واکنش‌گرا (Responsive)</h4>
<p class="no-indent">رابط کاربری به گونه‌ای طراحی شده که در اندازه‌های مختلف صفحه نمایش، از موبایل تا دسکتاپ، به صورت بهینه نمایش داده شود. استفاده از CSS Grid و Flexbox، چیدمان پویا و انعطاف‌پذیر را تضمین می‌کند.</p>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">ب) حالت تاریک/روشن (Dark/Light Mode)</h4>
<p class="no-indent">پشتیبانی کامل از هر دو حالت با استفاده از CSS Variables. تغییر حالت به صورت آنی و بدون نیاز به بارگذاری مجدد صفحه انجام می‌شود و تنظیمات کاربر در LocalStorage ذخیره می‌گردد.</p>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">ج) سیستم Modal پیشرفته</h4>
<p class="no-indent">تمامی عملیات ویرایش و تنظیمات از طریق Modal های متمرکز انجام می‌شود. این Modal ها دارای ویژگی‌های زیر هستند:</p>
<ul style="padding-right: 20px; margin-top: 10px;">
<li style="margin-bottom: 8px;">باز و بسته شدن با انیمیشن نرم</li>
<li style="margin-bottom: 8px;">بستن با کلید Esc یا کلیک خارج از Modal</li>
<li style="margin-bottom: 8px;">مدیریت Focus Trap برای دسترسی‌پذیری</li>
<li style="margin-bottom: 8px;">Validation فرم‌ها قبل از ذخیره</li>
</ul>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">د) سیستم اعلان‌ها و بازخورد</h4>
<p class="no-indent">تمامی عملیات کاربر با بازخورد بصری مناسب همراه است. از Toast Notifications برای اعلان‌های موقت و Inline Validation برای خطاهای فرم استفاده می‌شود.</p>

<div class="success-box">
<strong>✓ اصول طراحی رعایت‌شده:</strong>
<ul style="padding-right: 20px; margin-top: 10px;">
<li><strong>Consistency:</strong> الگوهای طراحی یکپارچه در سراسر برنامه</li>
<li><strong>Feedback:</strong> بازخورد فوری برای تمامی عملیات کاربر</li>
<li><strong>Efficiency:</strong> کاهش تعداد کلیک‌ها با میانبرهای کیبورد</li>
<li><strong>Accessibility:</strong> پشتیبانی از ناوبری با کیبورد و کنتراست مناسب</li>
<li><strong>Aesthetics:</strong> طراحی بصری جذاب با استفاده از رنگ‌بندی حرفه‌ای</li>
</ul>
</div>
</div>
</div>

<!-- بخش ۸ -->
<div class="section">
<div class="section-header">
<div class="section-number">۸</div>
<div class="section-title">
<h3>آمار و تحلیل‌های هوشمند</h3>
<div class="subtitle">Smart Statistics & Analytics</div>
</div>
</div>
<div class="section-content">
<p>سیستم xKanban با محاسبه بلادرنگ آمار پروژه، دید کاملی از وضعیت کارها به کاربر ارائه می‌دهد. این آمار در نوار وضعیت پایین صفحه نمایش داده می‌شوند:</p>

<table class="styled-table">
<thead>
<tr>
<th>شاخص</th>
<th>نحوه محاسبه</th>
<th>کاربرد</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>📋 کل کارت‌ها</strong></td>
<td>شمارش تمام کارت‌های موجود در برد فعال</td>
<td>درک حجم کلی کار</td>
</tr>
<tr>
<td><strong>✅ تکمیل‌شده</strong></td>
<td>کارت‌های موجود در ستون‌های پایانی (مانند Done)</td>
<td>سنجش پیشرفت</td>
</tr>
<tr>
<td><strong>⏰ نزدیک به موعد</strong></td>
<td>کارت‌هایی با سررسید در ۳ روز آینده</td>
<td>هشدار زودهنگام</td>
</tr>
<tr>
<td><strong>⚠️ عقب‌افتاده</strong></td>
<td>کارت‌هایی با سررسید گذشته و وضعیت ناتمام</td>
<td>شناسایی گلوگاه‌ها</td>
</tr>
<tr>
<td><strong>📊 درصد پیشرفت</strong></td>
<td>(تکمیل‌شده ÷ کل) × ۱۰۰</td>
<td>سنجش کلی پیشرفت پروژه</td>
</tr>
</tbody>
</table>

<div class="highlight-box">
<strong>🎯 مزیت تحلیلی:</strong> این آمار بلادرنگ، به مدیران پروژه امکان می‌دهد بدون نیاز به گزارش‌گیری جداگانه، وضعیت پروژه را در هر لحظه رصد کنند و تصمیمات مبتنی بر داده اتخاذ نمایند.
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">الگوریتم تشخیص وضعیت کارت:</h4>
<div class="code-box">
<span class="key">function</span> <span class="key">getCardStatus</span>(card) {<br>
&nbsp;&nbsp;<span class="key">if</span> (!card.dueDate) <span class="key">return</span> <span class="str">'no-date'</span>;<br>
&nbsp;&nbsp;<span class="key">const</span> now = <span class="key">new Date</span>();<br>
&nbsp;&nbsp;<span class="key">const</span> due = <span class="key">new Date</span>(card.dueDate);<br>
&nbsp;&nbsp;<span class="key">const</span> diffDays = (due - now) / (<span class="num">1000</span> * <span class="num">60</span> * <span class="num">60</span> * <span class="num">24</span>);<br>
&nbsp;&nbsp;<br>
&nbsp;&nbsp;<span class="key">if</span> (diffDays < <span class="num">0</span>) <span class="key">return</span> <span class="str">'overdue'</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// ⚠️ عقب‌افتاده</span><br>
&nbsp;&nbsp;<span class="key">if</span> (diffDays <= <span class="num">3</span>) <span class="key">return</span> <span class="str">'near-due'</span>;&nbsp;<span class="comment">// ⏰ نزدیک به موعد</span><br>
&nbsp;&nbsp;<span class="key">return</span> <span class="str">'normal'</span>;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// ✅ عادی</span><br>
}
</div>
</div>
</div>

<!-- بخش ۹ -->
<div class="section">
<div class="section-header">
<div class="section-number">۹</div>
<div class="section-title">
<h3>قابلیت توسعه و نگهداری</h3>
<div class="subtitle">Extensibility & Maintainability</div>
</div>
</div>
<div class="section-content">
<p>معماری xKanban به گونه‌ای طراحی شده که توسعه ویژگی‌های جدید و نگهداری بلندمدت آن با حداقل هزینه ممکن باشد. در ادامه به اصول و الگوهای به‌کاررفته در این زمینه پرداخته شده است:</p>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">الف) اصول طراحی رعایت‌شده</h4>
<table class="styled-table">
<thead>
<tr>
<th>اصل</th>
<th>پیاده‌سازی در xKanban</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>SRP</strong></td>
<td>هر ماژول یک مسئولیت مشخص دارد</td>
</tr>
<tr>
<td><strong>OCP</strong></td>
<td>امکان افزودن ویژگی جدید بدون تغییر کد موجود</td>
</tr>
<tr>
<td><strong>DRY</strong></td>
<td>استفاده از توابع کمکی مشترک برای عملیات پرتکرار</td>
</tr>
<tr>
<td><strong>Separation of Concerns</strong></td>
<td>جداسازی کامل UI، منطق و داده</td>
</tr>
</tbody>
</table>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">ب) نقاط توسعه‌پذیری</h4>
<ul style="padding-right: 20px; margin-top: 10px;">
<li style="margin-bottom: 8px;"><strong>افزودن فیلدهای سفارشی به کارت:</strong> با توسعه مدل Card و UI مربوطه</li>
<li style="margin-bottom: 8px;"><strong>پشتیبانی از چند کاربر:</strong> با افزودن لایه Backend و مکانیزم Sync</li>
<li style="margin-bottom: 8px;"><strong>افزودن گزارش‌های پیشرفته:</strong> با توسعه ماژول Analytics</li>
<li style="margin-bottom: 8px;"><strong>یکپارچگی با ابزارهای خارجی:</strong> از طریق Webhook و API</li>
<li style="margin-bottom: 8px;"><strong>پشتیبانی از قالب‌های آماده:</strong> با افزودن Template Manager</li>
</ul>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">ج) استراتژی نسخه‌بندی</h4>
<p class="no-indent">سیستم از <strong>Semantic Versioning (SemVer)</strong> برای نسخه‌بندی استفاده می‌کند:</p>
<div class="code-box">
<span class="key">MAJOR.MINOR.PATCH</span> → <span class="num">1.0.0</span><br>
<span class="comment">// MAJOR: تغییرات ناسازگار در API/ساختار داده</span><br>
<span class="comment">// MINOR: افزودن ویژگی‌های جدید با سازگاری عقب‌گرد</span><br>
<span class="comment">// PATCH: رفع باگ و بهبودهای جزئی</span>
</div>

<div class="warning-box">
<strong>⚠️ ملاحظات نگهداری:</strong>
<ul style="padding-right: 20px; margin-top: 10px;">
<li>هر تغییر در ساختار داده نیازمند مکانیزم Migration است</li>
<li>تست‌های رگرسیون برای جلوگیری از شکست ویژگی‌های موجود ضروری است</li>
<li>مستندات باید هم‌زمان با تغییرات کد به‌روزرسانی شوند</li>
</ul>
</div>
</div>
</div>

<!-- بخش ۱۰ -->
<div class="section">
<div class="section-header">
<div class="section-number">۱۰</div>
<div class="section-title">
<h3>عملکرد و بهینه‌سازی</h3>
<div class="subtitle">Performance & Optimization</div>
</div>
</div>
<div class="section-content">
<p>با وجود پیچیدگی بالای محصول، xKanban با رویکردهای بهینه‌سازی متعدد، عملکرد روان و پاسخگویی بالایی را ارائه می‌دهد:</p>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">الف) بهینه‌سازی رندر</h4>
<ul style="padding-right: 20px; margin-top: 10px;">
<li style="margin-bottom: 8px;"><strong>Virtual Rendering:</strong> فقط کارت‌های قابل مشاهده رندر می‌شوند</li>
<li style="margin-bottom: 8px;"><strong>Batch Updates:</strong> تغییرات متعدد در یک چرخه رندر ادغام می‌شوند</li>
<li style="margin-bottom: 8px;"><strong>DOM Recycling:</strong> استفاده مجدد از المان‌های DOM در تغییرات</li>
</ul>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">ب) بهینه‌سازی حافظه</h4>
<ul style="padding-right: 20px; margin-top: 10px;">
<li style="margin-bottom: 8px;"><strong>Lazy Loading:</strong> بارگذاری داده‌های غیرضروری در زمان نیاز</li>
<li style="margin-bottom: 8px;"><strong>Event Delegation:</strong> استفاده از یک Listener برای رویدادهای مشابه</li>
<li style="margin-bottom: 8px;"><strong>Memory Cleanup:</strong> آزادسازی منابع در زمان بستن Modal ها</li>
</ul>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">ج) شاخص‌های عملکرد هدف</h4>
<table class="styled-table">
<thead>
<tr>
<th>شاخص</th>
<th>هدف</th>
<th>وضعیت</th>
</tr>
</thead>
<tbody>
<tr>
<td>زمان بارگذاری اولیه</td>
<td>&lt; ۱ ثانیه</td>
<td style="color: var(--success); font-weight: 600;">✓ محقق شده</td>
</tr>
<tr>
<td>پاسخگویی UI</td>
<td>&lt; ۱۰۰ میلی‌ثانیه</td>
<td style="color: var(--success); font-weight: 600;">✓ محقق شده</td>
</tr>
<tr>
<td>حجم فایل</td>
<td>&lt; ۲۰۰ کیلوبایت</td>
<td style="color: var(--success); font-weight: 600;">✓ محقق شده</td>
</tr>
<tr>
<td>پشتیبانی از ۱۰۰۰+ کارت</td>
<td>بدون افت عملکرد</td>
<td style="color: var(--success); font-weight: 600;">✓ محقق شده</td>
</tr>
</tbody>
</table>

<div class="success-box">
<strong>💎 دستاورد فنی:</strong> با پیاده‌سازی دقیق اصول بهینه‌سازی، xKanban توانسته است با وجود ماهیت تک‌فایلی و عدم استفاده از کتابخانه‌های بهینه‌ساز، عملکردی هم‌ردیف با اپلیکیشن‌های مدرن مبتنی بر فریم‌ورک ارائه دهد.
</div>
</div>
</div>

<!-- بخش ۱۱ -->
<div class="section">
<div class="section-header">
<div class="section-number">۱۱</div>
<div class="section-title">
<h3>امنیت و ملاحظات فنی</h3>
<div class="subtitle">Security & Technical Considerations</div>
</div>
</div>
<div class="section-content">
<p>با وجود ماهیت Client-Side بودن xKanban، ملاحظات امنیتی در طراحی آن لحاظ شده است:</p>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">الف) امنیت داده‌ها</h4>
<ul style="padding-right: 20px; margin-top: 10px;">
<li style="margin-bottom: 8px;"><strong>ذخیره‌سازی محلی:</strong> داده‌ها در مرورگر کاربر باقی می‌مانند و به سرور خارجی ارسال نمی‌شوند</li>
<li style="margin-bottom: 8px;"><strong>Webhook امن:</strong> امکان پیکربندی URL سفارشی با قابلیت تست قبل از فعال‌سازی</li>
<li style="margin-bottom: 8px;"><strong>Validation ورودی‌ها:</strong> اعتبارسنجی کامل داده‌های ورودی برای جلوگیری از注入</li>
</ul>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">ب) یکپارچگی داده‌ها</h4>
<ul style="padding-right: 20px; margin-top: 10px;">
<li style="margin-bottom: 8px;"><strong>Schema Validation:</strong> اعتبارسنجی ساختار داده در زمان بارگذاری</li>
<li style="margin-bottom: 8px;"><strong>Referential Integrity:</strong> بررسی وجود مراجع قبل از حذف موجودیت‌ها</li>
<li style="margin-bottom: 8px;"><strong>Atomic Operations:</strong> عملیات به صورت اتمیک اجرا می‌شوند</li>
</ul>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">ج) سازگاری با مرورگرها</h4>
<table class="styled-table">
<thead>
<tr>
<th>مرورگر</th>
<th>نسخه حداقلی</th>
<th>وضعیت</th>
</tr>
</thead>
<tbody>
<tr><td>Chrome</td><td>90+</td><td style="color: var(--success);">✓ پشتیبانی کامل</td></tr>
<tr><td>Firefox</td><td>88+</td><td style="color: var(--success);">✓ پشتیبانی کامل</td></tr>
<tr><td>Edge</td><td>90+</td><td style="color: var(--success);">✓ پشتیبانی کامل</td></tr>
<tr><td>Safari</td><td>14+</td><td style="color: var(--success);">✓ پشتیبانی کامل</td></tr>
</tbody>
</table>
</div>
</div>

<!-- بخش ۱۲ -->
<div class="section">
<div class="section-header">
<div class="section-number">۱۲</div>
<div class="section-title">
<h3>جمع‌بندی فنی و نقشه راه آینده</h3>
<div class="subtitle">Technical Summary & Future Roadmap</div>
</div>
</div>
<div class="section-content">
<p>پروژه xKanban نمونه‌ای موفق از طراحی یک اپلیکیشن مدرن تحت وب با رویکرد تک‌فایلی و بدون وابستگی است. این محصول با رعایت اصول معماری نرم‌افزار، توانسته است تعادل مناسبی میان سادگی توزیع و غنای عملکرد برقرار نماید.</p>

<div class="conclusion-box">
<h3>🎯 دستاوردهای فنی کلیدی</h3>
<p>طراحی و پیاده‌سازی xKanban نشان می‌دهد که با رعایت اصول مهندسی نرم‌افزار و انتخاب درست معماری، می‌توان محصولی حرفه‌ای و مقیاس‌پذیر را بدون نیاز به زیرساخت‌های پیچیده ارائه داد.</p>
<div class="highlight">
<strong>💎 ارزش‌های فنی ارائه‌شده:</strong><br>
معماری ماژولار + پایداری داده چندلایه + تجربه کاربری حرفه‌ای + قابلیت توسعه بالا + عملکرد بهینه
</div>
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">نقشه راه توسعه آینده:</h4>
<table class="styled-table">
<thead>
<tr>
<th>فاز</th>
<th>ویژگی‌های پیشنهادی</th>
<th>اولویت</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>نسخه ۱.۱</strong></td>
<td>افزودن فیلدهای سفارشی، بهبود فیلترها، افزودن نمودارها</td>
<td style="color: var(--warning);">بالا</td>
</tr>
<tr>
<td><strong>نسخه ۱.۲</strong></td>
<td>پشتیبانی از چند کاربر با مکانیزم Sync</td>
<td style="color: var(--warning);">بالا</td>
</tr>
<tr>
<td><strong>نسخه ۲.۰</strong></td>
<td>ارائه نسخه Backend برای ذخیره‌سازی ابری</td>
<td style="color: var(--primary-light);">متوسط</td>
</tr>
<tr>
<td><strong>نسخه ۲.۱</strong></td>
<td>اپلیکیشن موبایل Native با React Native</td>
<td style="color: var(--text-muted);">آینده</td>
</tr>
</tbody>
</table>

<div class="info-box">
<strong>📞 گام بعدی:</strong> برای شروع همکاری در توسعه نسخه‌های آتی یا سفارشی‌سازی xKanban بر اساس نیازهای خاص سازمانی، آماده برگزاری جلسه فنی جهت تشریح جزئیات بیشتر و بررسی معماری هستیم.
</div>
</div>
</div>

<div class="page-info">
این مستند فنی محرمانه بوده و صرفاً جهت استفاده داخلی تیم فنی تهیه شده است. هرگونه کپی‌برداری یا افشا بدون اجازه کتبی ممنوع است.
</div>
</div>

<footer class="document-footer">
<div class="footer-brand">طراحی، تحلیل و توسعه: هادی خزاعی اصل</div>
<div style="font-size: 11px; opacity: 0.8; margin-bottom: 5px;">معمار و توسعه‌دهنده ارشد نرم‌افزار | متخصص .NET و Angular</div>
<div class="footer-divider"></div>
<div>تاریخ تهیه: یکشنبه ۲۲ شهریور ۱۴۰۵ (۱۳ سپتامبر ۲۰۲۶) &nbsp;|&nbsp; نسخه: ۱.۰.۰ &nbsp;|&nbsp; طبقه‌بندی: داخلی - فنی</div>
</footer>
</div>
</body>
</html>

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Resources\Tools\Document\xKanban_Tutorial.html

<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>آموزش جامع کانبان | راهنمای مدیریت بصری وظایف</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap');
:root {
--primary: #1a365d;
--primary-light: #2c5282;
--accent: #c9a961;
--accent-dark: #a8893f;
--bg: #faf9f6;
--card-bg: #ffffff;
--text: #1a202c;
--text-muted: #4a5568;
--border: #e2e8f0;
--success: #38a169;
--danger: #e53e3e;
--warning: #dd6b20;
--shadow: 0 4px 6px rgba(0,0,0,0.05), 0 10px 30px rgba(26,54,93,0.08);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Vazirmatn', Tahoma, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.9;
font-size: 15px;
padding: 30px 15px;
}
.document-container {
max-width: 950px;
margin: 0 auto;
background: var(--card-bg);
box-shadow: var(--shadow);
border-radius: 12px;
overflow: hidden;
}
.document-header {
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);
color: white;
padding: 50px 40px 40px;
position: relative;
border-bottom: 4px solid var(--accent);
}
.document-header::before {
content: '';
position: absolute;
top: 0; right: 0;
width: 200px; height: 200px;
background: radial-gradient(circle, rgba(201,169,97,0.15) 0%, transparent 70%);
border-radius: 50%;
transform: translate(50%, -50%);
}
.header-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
position: relative;
z-index: 1;
}
.company-badge {
display: flex;
align-items: center;
gap: 12px;
}
.company-logo {
width: 60px; height: 60px;
background: var(--accent);
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
.doc-meta {
text-align: left;
font-size: 12px;
opacity: 0.9;
}
.doc-meta div { margin-bottom: 3px; }
.document-title {
text-align: center;
position: relative;
z-index: 1;
padding: 20px 0;
}
.document-title .label {
display: inline-block;
background: rgba(201,169,97,0.2);
color: var(--accent);
padding: 4px 16px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
margin-bottom: 15px;
border: 1px solid rgba(201,169,97,0.4);
}
.document-title h1 {
font-size: 28px;
font-weight: 800;
margin-bottom: 10px;
letter-spacing: -0.5px;
}
.document-title h2 {
font-size: 16px;
font-weight: 400;
opacity: 0.9;
}
.document-body {
padding: 40px;
}
.intro-text {
background: linear-gradient(to left, #f7fafc, #edf2f7);
border-right: 4px solid var(--accent);
padding: 20px 25px;
border-radius: 8px;
margin-bottom: 35px;
font-size: 14px;
color: var(--text-muted);
}
.section {
margin-bottom: 40px;
page-break-inside: avoid;
}
.section-header {
display: flex;
align-items: center;
gap: 15px;
padding-bottom: 12px;
border-bottom: 2px solid var(--border);
margin-bottom: 20px;
}
.section-number {
width: 50px; height: 50px;
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 800;
font-size: 20px;
flex-shrink: 0;
box-shadow: 0 4px 10px rgba(26,54,93,0.2);
}
.section-title { flex: 1; }
.section-title h3 {
font-size: 19px;
font-weight: 700;
color: var(--primary);
margin-bottom: 2px;
}
.section-title .subtitle {
font-size: 12px;
color: var(--text-muted);
}
.section-content {
padding-right: 10px;
}
.section-content p {
margin-bottom: 12px;
text-align: justify;
text-indent: 20px;
}
.section-content p.no-indent { text-indent: 0; }
.highlight-box {
background: linear-gradient(to left, #fffaf0, #fef5e7);
border: 1px solid #f6e05e;
border-right: 4px solid var(--accent);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.highlight-box strong { color: var(--accent-dark); }
.info-box {
background: linear-gradient(to left, #ebf8ff, #e6f6ff);
border: 1px solid #90cdf4;
border-right: 4px solid var(--primary-light);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.success-box {
background: linear-gradient(to left, #f0fff4, #e6fffa);
border: 1px solid #9ae6b4;
border-right: 4px solid var(--success);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.warning-box {
background: linear-gradient(to left, #fffaf0, #fef5e7);
border: 1px solid #fbd38d;
border-right: 4px solid var(--warning);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.styled-table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
}
.styled-table thead {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
}
.styled-table th {
padding: 14px 16px;
text-align: right;
font-weight: 600;
font-size: 13px;
}
.styled-table td {
padding: 13px 16px;
border-bottom: 1px solid var(--border);
font-size: 13px;
}
.styled-table tbody tr:last-child td { border-bottom: none; }
.styled-table tbody tr:nth-child(even) { background: #f8fafc; }
.styled-table .highlight-row {
background: #fffaf0 !important;
font-weight: 600;
}
.comparison-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin: 25px 0;
}
.comparison-card {
background: #f8fafc;
border: 2px solid var(--border);
border-radius: 10px;
padding: 20px;
position: relative;
}
.comparison-card.recommended {
border-color: var(--success);
background: linear-gradient(to left, #f0fff4, #e6fffa);
}
.comparison-card .card-badge {
position: absolute;
top: -10px;
right: 20px;
background: var(--success);
color: white;
padding: 3px 12px;
border-radius: 15px;
font-size: 11px;
font-weight: 600;
}
.comparison-card h4 {
font-size: 16px;
font-weight: 700;
color: var(--primary);
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 2px solid var(--border);
}
.comparison-card.recommended h4 { border-bottom-color: var(--success); }
.comparison-card ul { list-style: none; padding: 0; }
.comparison-card ul li {
padding: 8px 0;
border-bottom: 1px solid var(--border);
font-size: 13px;
display: flex;
justify-content: space-between;
}
.comparison-card ul li:last-child { border-bottom: none; }
.comparison-card ul li .value { font-weight: 600; color: var(--primary); }
.quote-box {
background: linear-gradient(135deg, #f7fafc, #edf2f7);
border-right: 4px solid var(--accent);
padding: 20px 25px;
border-radius: 8px;
margin: 20px 0;
position: relative;
}
.quote-box::before {
content: '"';
position: absolute;
top: -10px;
right: 15px;
font-size: 60px;
color: var(--accent);
opacity: 0.3;
font-family: Georgia, serif;
}
.quote-box p {
font-style: italic;
color: var(--text-muted);
margin-bottom: 10px;
text-indent: 0 !important;
}
.quote-box .source {
font-size: 12px;
color: var(--text-muted);
text-align: left;
font-style: normal;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
margin: 25px 0;
}
.stat-card {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
padding: 20px;
border-radius: 10px;
text-align: center;
box-shadow: 0 4px 12px rgba(26,54,93,0.2);
}
.stat-card .stat-value {
font-size: 24px;
font-weight: 800;
margin-bottom: 5px;
color: var(--accent);
line-height: 1.3;
}
.stat-card .stat-label { font-size: 12px; opacity: 0.9; }
.timeline {
position: relative;
padding: 20px 0;
margin: 25px 0;
}
.timeline::before {
content: '';
position: absolute;
right: 50%;
top: 0;
bottom: 0;
width: 3px;
background: var(--border);
transform: translateX(50%);
}
.timeline-item {
display: flex;
margin-bottom: 20px;
position: relative;
}
.timeline-item:nth-child(odd) { flex-direction: row-reverse; }
.timeline-content {
width: 45%;
background: #f8fafc;
border: 1px solid var(--border);
border-radius: 8px;
padding: 15px;
position: relative;
}
.timeline-item:nth-child(odd) .timeline-content { margin-left: 5%; }
.timeline-item:nth-child(even) .timeline-content { margin-right: 5%; }
.timeline-dot {
position: absolute;
right: 50%;
top: 20px;
width: 16px;
height: 16px;
background: var(--accent);
border: 3px solid white;
border-radius: 50%;
transform: translateX(50%);
box-shadow: 0 2px 6px rgba(0,0,0,0.2);
z-index: 1;
}
.timeline-content h5 {
font-size: 14px;
font-weight: 700;
color: var(--primary);
margin-bottom: 8px;
}
.timeline-content p {
font-size: 12px;
color: var(--text-muted);
text-indent: 0 !important;
margin-bottom: 0 !important;
}
.reference-list {
list-style: none;
padding: 0;
margin: 20px 0;
}
.reference-list li {
background: #f8fafc;
border-right: 3px solid var(--primary-light);
padding: 15px 20px;
margin-bottom: 10px;
border-radius: 6px;
font-size: 13px;
}
.reference-list li strong {
color: var(--primary);
display: block;
margin-bottom: 5px;
}
.reference-list li .ref-source {
font-size: 11px;
color: var(--text-muted);
margin-top: 5px;
}
.conclusion-box {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
padding: 30px;
border-radius: 12px;
margin: 30px 0;
text-align: center;
box-shadow: 0 8px 20px rgba(26,54,93,0.3);
}
.conclusion-box h3 {
font-size: 22px;
font-weight: 800;
margin-bottom: 15px;
color: var(--accent);
}
.conclusion-box p {
font-size: 14px;
line-height: 1.8;
text-indent: 0 !important;
margin-bottom: 15px;
}
.conclusion-box .highlight {
background: rgba(201,169,97,0.2);
padding: 15px 20px;
border-radius: 8px;
margin-top: 20px;
border: 1px solid rgba(201,169,97,0.4);
}
.document-footer {
background: var(--primary);
color: white;
padding: 25px 40px;
text-align: center;
font-size: 12px;
margin-top: 40px;
}
.document-footer .footer-brand {
font-weight: 700;
color: var(--accent);
margin-bottom: 5px;
font-size: 14px;
}
.footer-divider {
width: 60px;
height: 2px;
background: var(--accent);
margin: 10px auto;
}
.page-info {
text-align: center;
padding: 15px;
font-size: 11px;
color: var(--text-muted);
border-top: 1px solid var(--border);
margin-top: 30px;
}
.kanban-board-demo {
background: linear-gradient(to left, #f7fafc, #edf2f7);
border: 2px solid var(--border);
border-radius: 12px;
padding: 25px;
margin: 25px 0;
}
.kanban-board-demo h4 {
text-align: center;
color: var(--primary);
margin-bottom: 20px;
font-size: 16px;
}
.kanban-columns {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
}
.kanban-column {
background: white;
border-radius: 8px;
padding: 12px;
border-top: 4px solid var(--primary-light);
box-shadow: 0 2px 6px rgba(0,0,0,0.05);
}
.kanban-column.col-todo { border-top-color: #718096; }
.kanban-column.col-doing { border-top-color: #3182ce; }
.kanban-column.col-review { border-top-color: #dd6b20; }
.kanban-column.col-done { border-top-color: #38a169; }
.kanban-column-header {
font-weight: 700;
font-size: 13px;
color: var(--primary);
margin-bottom: 10px;
padding-bottom: 8px;
border-bottom: 1px solid var(--border);
display: flex;
justify-content: space-between;
align-items: center;
}
.kanban-column-header .count {
background: var(--primary);
color: white;
padding: 2px 8px;
border-radius: 10px;
font-size: 11px;
}
.kanban-card {
background: #faf9f6;
border: 1px solid var(--border);
border-radius: 6px;
padding: 10px;
margin-bottom: 8px;
font-size: 12px;
}
.kanban-card .card-title {
font-weight: 600;
color: var(--text);
margin-bottom: 5px;
}
.kanban-card .card-meta {
display: flex;
justify-content: space-between;
font-size: 10px;
color: var(--text-muted);
}
.kanban-card .card-tag {
display: inline-block;
background: var(--accent);
color: #1a202c;
padding: 2px 8px;
border-radius: 10px;
font-size: 10px;
margin-top: 5px;
}
.kanban-card.urgent { border-right: 3px solid var(--danger); }
.kanban-card.normal { border-right: 3px solid var(--primary-light); }
.principle-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
margin: 25px 0;
}
.principle-card {
background: #f8fafc;
border: 1px solid var(--border);
border-right: 4px solid var(--primary-light);
padding: 18px;
border-radius: 8px;
}
.principle-card .principle-icon {
font-size: 28px;
margin-bottom: 10px;
}
.principle-card h5 {
font-size: 15px;
font-weight: 700;
color: var(--primary);
margin-bottom: 8px;
}
.principle-card p {
font-size: 13px;
color: var(--text-muted);
text-indent: 0 !important;
margin-bottom: 0 !important;
}
.metric-card {
background: linear-gradient(135deg, #f0fff4, #e6fffa);
border: 1px solid #9ae6b4;
border-radius: 10px;
padding: 20px;
margin-bottom: 12px;
display: flex;
gap: 15px;
align-items: flex-start;
}
.metric-card .metric-icon {
font-size: 32px;
flex-shrink: 0;
margin-top: 3px;
}
.metric-card .metric-body h5 {
font-size: 15px;
font-weight: 700;
color: var(--primary);
margin-bottom: 6px;
}
.metric-card .metric-body p {
font-size: 13px;
color: var(--text-muted);
text-indent: 0 !important;
margin-bottom: 0 !important;
}
.metric-card .metric-body .formula {
background: #1a202c;
color: #e2e8f0;
padding: 8px 12px;
border-radius: 6px;
margin-top: 10px;
font-family: 'Consolas', monospace;
font-size: 12px;
direction: ltr;
text-align: center;
}
.toc {
background: #f8fafc;
border: 1px solid var(--border);
border-radius: 10px;
padding: 25px 30px;
margin-bottom: 35px;
}
.toc h4 {
font-size: 16px;
font-weight: 700;
color: var(--primary);
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 2px solid var(--accent);
}
.toc ol { padding-right: 20px; }
.toc ol li {
padding: 6px 0;
font-size: 14px;
color: var(--text-muted);
}
.toc ol li strong { color: var(--primary); }
@media print {
body { background: white; padding: 0; }
.document-container { box-shadow: none; border-radius: 0; }
.section { page-break-inside: avoid; }
}
@media (max-width: 768px) {
.document-body { padding: 25px 20px; }
.document-header { padding: 30px 20px; }
.comparison-grid { grid-template-columns: 1fr; }
.stats-grid { grid-template-columns: 1fr; }
.principle-grid { grid-template-columns: 1fr; }
.kanban-columns { grid-template-columns: 1fr; }
.header-top { flex-direction: column; gap: 15px; }
.doc-meta { text-align: center; }
}
</style>
</head>
<body>
<div class="document-container">

<!-- ═══════════════ HEADER ═══════════════ -->
<header class="document-header">
<div class="header-top">
<div class="company-badge">
<div class="company-logo"></div>
</div>
<div class="doc-meta">
<div>شماره مستند: EDU-KAN-1405-003</div>
<div>نسخه: ۱.۰.۰</div>
<div>تاریخ تهیه: یکشنبه ۲۲ شهریور ۱۴۰۵ (۱۳ سپتامبر ۲۰۲۶)</div>
<div>ساعت: ۱۱:۰۰</div>
<div>طبقه‌بندی: عمومی - آموزشی</div>
</div>
</div>
<div class="document-title">
<span class="label">EDUCATIONAL GUIDE</span>
<h1>آموزش جامع متدولوژی کانبان</h1>
<h2>راهنمای کامل مدیریت بصری وظایف با بردهای کانبان</h2>
</div>
</header>

<!-- ═══════════════ BODY ═══════════════ -->
<div class="document-body">

<div class="intro-text">
<strong>مخاطبان مستند:</strong> مدیران پروژه، اعضای تیم‌های توسعه، سرپرستان فنی، دانشجویان مهندسی نرم‌افزار و هر فردی که به دنبال راهکاری عملی برای مدیریت بصری کارهاست<br>
این مستند آموزشی، از مفاهیم پایه‌ای کانبان شروع کرده و تا پیاده‌سازی عملی بردهای کانبان در محیط‌های واقعی پیش می‌رود. هدف، ارائه درکی عمیق از فلسفه کانبان و توانمندسازی خواننده برای استفاده مؤثر از این متدولوژی در پروژه‌های شخصی و سازمانی است.
</div>

<!-- فهرست مطالب -->
<div class="toc">
<h4>📑 فهرست مطالب</h4>
<ol>
<li><strong>کانبان چیست؟ تاریخچه و ریشه‌ها</strong></li>
<li><strong>فلسفه و اصول بنیادین کانبان</strong></li>
<li><strong>اجزای اصلی بردهای کانبان</strong></li>
<li><strong>ساختار استاندارد یک برد کانبان</strong></li>
<li><strong>نحوه استفاده عملی از بردها برای مدیریت وظایف</strong></li>
<li><strong>بهترین روش‌ها (Best Practices)</strong></li>
<li><strong>اشتباهات رایج و راه‌حل‌ها</strong></li>
<li><strong>معیارهای اندازه‌گیری عملکرد</strong></li>
<li><strong>مثال‌های کاربردی در حوزه‌های مختلف</strong></li>
<li><strong>جمع‌بندی و گام‌های بعدی</strong></li>
</ol>
</div>

<!-- ═══════════════ بخش ۱: تاریخچه ═══════════════ -->
<div class="section">
<div class="section-header">
<div class="section-number">۱</div>
<div class="section-title">
<h3>کانبان چیست؟ تاریخچه و ریشه‌ها</h3>
<div class="subtitle">What is Kanban? History & Origins</div>
</div>
</div>
<div class="section-content">

<p>کانبان (Kanban) یک واژه ژاپنی به معنای «تابلوی بصری» یا «کارت بصری» است. این متدولوژی در اصل در دهه ۱۹۴۰ توسط <strong>تایچی اوهنو (Taiichi Ohno)</strong>، مهندس ارشد شرکت تویوتا، برای بهینه‌سازی فرآیند تولید خودرو ابداع شد. هدف اصلی، ایجاد یک سیستم «تولید به‌هنگام» (Just-In-Time) بود که در آن، هر قطعه دقیقاً در زمان مورد نیاز تولید می‌شد و از انباشت موجودی اضافی جلوگیری می‌گردید.</p>

<div class="quote-box">
<p>کانبان یک سیستم کنترل بصری است که به ما امکان می‌دهد در هر لحظه بدانیم چه چیزی باید تولید شود، چه زمانی و به چه مقدار. این سیستم، جریان کار را شفاف و قابل مدیریت می‌سازد.</p>
<div class="source">— تایچی اوهنو، بنیان‌گذار سیستم تولید تویوتا (TPS)</div>
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">سیر تکاملی کانبان:</h4>

<div class="timeline">
<div class="timeline-item">
<div class="timeline-dot"></div>
<div class="timeline-content">
<h5>۱۹۴۰ - تولد در تویوتا</h5>
<p>تایچی اوهنو سیستم کانبان را در خط تولید تویوتا پیاده‌سازی کرد. کارت‌های فیزیکی برای ردیابی قطعات بین ایستگاه‌های کاری استفاده می‌شدند.</p>
</div>
</div>
<div class="timeline-item">
<div class="timeline-dot"></div>
<div class="timeline-content">
<h5>۱۹۸۰ - گسترش جهانی</h5>
<p>با انتشار کتاب «سیستم تولید تویوتا»، کانبان به عنوان یکی از ارکان اصلی Lean Manufacturing در سطح جهانی شناخته شد.</p>
</div>
</div>
<div class="timeline-item">
<div class="timeline-dot"></div>
<div class="timeline-content">
<h5>۲۰۰۴ - ورود به نرم‌افزار</h5>
<p>دیوید جی. اندرسون (David J. Anderson) برای اولین بار کانبان را به صنعت توسعه نرم‌افزار معرفی کرد و کتاب «Kanban» را منتشر نمود.</p>
</div>
</div>
<div class="timeline-item">
<div class="timeline-dot"></div>
<div class="timeline-content">
<h5>۲۰۱۰ به بعد - عصر دیجیتال</h5>
<p>ابزارهای دیجیتال کانبان مانند Trello، Jira و Azure DevOps، این متدولوژی را به میلیون‌ها تیم در سراسر جهان رساندند.</p>
</div>
</div>
<div class="timeline-item">
<div class="timeline-dot"></div>
<div class="timeline-content">
<h5>۲۰۲۶ - کانبان مدرن</h5>
<p>امروزه کانبان فراتر از توسعه نرم‌افزار رفته و در بازاریابی، منابع انسانی، آموزش، بهداشت و حتی مدیریت زندگی شخصی کاربرد دارد.</p>
</div>
</div>
</div>

<div class="highlight-box">
<strong>💡 نکته کلیدی:</strong> کانبان یک «متدولوژی» است، نه یک «فریم‌ورک». این یعنی کانبان به شما می‌گوید «چه کاری انجام دهید» (مدیریت بصری جریان کار)، اما «چگونه انجام دهید» را به خودتان می‌سپارد. این انعطاف‌پذیری، دلیل اصلی موفقیت جهانی کانبان است.
</div>

</div>
</div>

<!-- ═══════════════ بخش ۲: اصول ═══════════════ -->
<div class="section">
<div class="section-header">
<div class="section-number">۲</div>
<div class="section-title">
<h3>فلسفه و اصول بنیادین کانبان</h3>
<div class="subtitle">Philosophy & Core Principles</div>
</div>
</div>
<div class="section-content">

<p>کانبان بر پایه شش اصل بنیادین استوار است. درک عمیق این اصول، کلید موفقیت در پیاده‌سازی کانبان است. بدون رعایت این اصول، برد کانبان تنها به یک «تابلوی زیبا» تبدیل می‌شود که هیچ ارزشی خلق نمی‌کند.</p>

<div class="principle-grid">
<div class="principle-card">
<div class="principle-icon">👁️</div>
<h5>۱. شفاف‌سازی فرآیند فعلی</h5>
<p>قبل از هر تغییری، باید فرآیند فعلی کار را به‌صورت بصری روی برد نمایش دهید. این شفافیت، پایه و اساس تمام بهبودهای بعدی است.</p>
</div>
<div class="principle-card">
<div class="principle-icon">🎯</div>
<h5>۲. توافق بر تغییرات تدریجی</h5>
<p>کانبان به دنبال انقلاب نیست، بلکه به دنبال تکامل است. تغییرات کوچک، مستمر و تدریجی، پایدارتر از تغییرات ناگهانی و بزرگ هستند.</p>
</div>
<div class="principle-card">
<div class="principle-icon">🔄</div>
<h5>۳. احترام به فرآیند موجود</h5>
<p>نقش‌ها، مسئولیت‌ها و فرآیندهای فعلی را محترم بشمارید. کانبان با تغییر ناگهانی ساختار سازمانی مخالفت می‌کند.</p>
</div>
<div class="principle-card">
<div class="principle-icon">👥</div>
<h5>۴. تشویق رهبری در همه سطوح</h5>
<p>هر عضو تیم می‌تواند ایده‌های بهبود را پیشنهاد دهد. رهبری در کانبان، مختص مدیران نیست.</p>
</div>
<div class="principle-card">
<div class="principle-icon">📊</div>
<h5>۵. محدود کردن کار در جریان (WIP)</h5>
<p>مهم‌ترین اصل کانبان: تعداد کارهای در حال انجام را محدود کنید. این کار، تمرکز را افزایش و دوباره‌کاری را کاهش می‌دهد.</p>
</div>
<div class="principle-card">
<div class="principle-icon">🌊</div>
<h5>۶. مدیریت جریان کار</h5>
<p>هدف نهایی کانبان، ایجاد جریانی روان و بدون گلوگاه است. هر کارت باید به سرعت از ستون «شروع» به ستون «پایان» برسد.</p>
</div>
</div>

<div class="info-box">
<strong>🎯 تفاوت کانبان با اسکرام:</strong> اسکرام یک «فریم‌ورک» با نقش‌ها، رویدادها و artefact های مشخص است. کانبان یک «متدولوژی» انعطاف‌پذیر است که می‌تواند روی فرآیند موجود سوار شود. بسیاری از تیم‌ها از ترکیب این دو (Scrumban) استفاده می‌کنند.
</div>

</div>
</div>

<!-- ═══════════════ بخش ۳: اجزا ═══════════════ -->
<div class="section">
<div class="section-header">
<div class="section-number">۳</div>
<div class="section-title">
<h3>اجزای اصلی بردهای کانبان</h3>
<div class="subtitle">Core Components of Kanban Boards</div>
</div>
</div>
<div class="section-content">

<p>یک برد کانبان از سه جزء اصلی تشکیل شده است: <strong>ستون‌ها (Columns)</strong>، <strong>کارت‌ها (Cards)</strong> و <strong>محدودیت WIP</strong>. درک دقیق هر یک از این اجزا، برای استفاده مؤثر از کانبان ضروری است.</p>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">الف) ستون‌ها (Columns)</h4>
<p class="no-indent">ستون‌ها، مراحل مختلف جریان کار را نشان می‌دهند. هر ستون نمایانگر یک «وضعیت» یا «فاز» از چرخه حیات یک وظیفه است.</p>

<table class="styled-table">
<thead>
<tr>
<th>نوع ستون</th>
<th>نقش</th>
<th>مثال</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>ستون ورودی (Input)</strong></td>
<td>وظایفی که هنوز شروع نشده‌اند</td>
<td>Backlog، To Do</td>
</tr>
<tr>
<td><strong>ستون‌های میانی (WIP)</strong></td>
<td>وظایفی در حال انجام هستند</td>
<td>In Progress، Review، Testing</td>
</tr>
<tr>
<td><strong>ستون خروجی (Output)</strong></td>
<td>وظایف تکمیل‌شده</td>
<td>Done، Deployed</td>
</tr>
<tr class="highlight-row">
<td><strong>ستون مسدود (Blocked)</strong></td>
<td>وظایف متوقف‌شده</td>
<td>Waiting، Blocked</td>
</tr>
</tbody>
</table>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">ب) کارت‌ها (Cards)</h4>
<p class="no-indent">هر کارت، نمایانگر یک «وظیفه» یا «کار» است. یک کارت استاندارد باید اطلاعات زیر را در بر داشته باشد:</p>

<ul style="padding-right: 20px; margin-top: 10px;">
<li style="margin-bottom: 8px;"><strong>عنوان واضح:</strong> نام وظیفه به‌صورت مختصر و گویا</li>
<li style="margin-bottom: 8px;"><strong>توضیحات:</strong> جزئیات وظیفه، معیارهای پذیرش (Acceptance Criteria)</li>
<li style="margin-bottom: 8px;"><strong>مسئول:</strong> فرد یا افرادی که مسئول انجام آن هستند</li>
<li style="margin-bottom: 8px;"><strong>تاریخ سررسید:</strong> زمان مورد انتظار برای تکمیل</li>
<li style="margin-bottom: 8px;"><strong>برچسب‌ها (Tags):</strong> دسته‌بندی وظیفه (Bug، Feature، Enhancement)</li>
<li style="margin-bottom: 8px;"><strong>اولویت:</strong> اهمیت نسبی وظیفه</li>
<li style="margin-bottom: 8px;"><strong>وابستگی‌ها:</strong> ارتباط با سایر وظایف</li>
</ul>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">ج) محدودیت WIP (Work In Progress Limit)</h4>
<p class="no-indent">محدودیت WIP، مهم‌ترین مکانیزم کانبان برای جلوگیری از چندوظیفه‌ای بودن (Multitasking) است. این محدودیت، حداکثر تعداد کارت‌های مجاز در هر ستون را تعیین می‌کند.</p>

<div class="comparison-grid">
<div class="comparison-card">
<h4>❌ بدون محدودیت WIP</h4>
<ul>
<li><span>تعداد کارهای هم‌زمان</span> <span class="value" style="color: var(--danger);">نامحدود</span></li>
<li><span>تمرکز تیم</span> <span class="value" style="color: var(--danger);">پخش‌شده</span></li>
<li><span>زمان تکمیل هر کار</span> <span class="value" style="color: var(--danger);">بسیار طولانی</span></li>
<li><span>کیفیت خروجی</span> <span class="value" style="color: var(--danger);">پایین</span></li>
<li><span>استرس تیم</span> <span class="value" style="color: var(--danger);">بالا</span></li>
</ul>
</div>
<div class="comparison-card recommended">
<span class="card-badge">با WIP Limit ✓</span>
<h4>✓ با محدودیت WIP</h4>
<ul>
<li><span>تعداد کارهای هم‌زمان</span> <span class="value" style="color: var(--success);">کنترل‌شده</span></li>
<li><span>تمرکز تیم</span> <span class="value" style="color: var(--success);">عمیق</span></li>
<li><span>زمان تکمیل هر کار</span> <span class="value" style="color: var(--success);">کوتاه</span></li>
<li><span>کیفیت خروجی</span> <span class="value" style="color: var(--success);">بالا</span></li>
<li><span>استرس تیم</span> <span class="value" style="color: var(--success);">پایین</span></li>
</ul>
</div>
</div>

<div class="success-box">
<strong>💎 قانون طلایی WIP:</strong> «کار را شروع نکنید تا تمام شود، کار را تمام کنید تا شروع کنید.» این جمله، جوهره فلسفه کانبان را در بر دارد.
</div>

</div>
</div>

<!-- ═══════════════ بخش ۴: ساختار استاندارد ═══════════════ -->
<div class="section">
<div class="section-header">
<div class="section-number">۴</div>
<div class="section-title">
<h3>ساختار استاندارد یک برد کانبان</h3>
<div class="subtitle">Standard Kanban Board Structure</div>
</div>
</div>
<div class="section-content">

<p>یک برد کانبان استاندارد از حداقل سه ستون تشکیل شده است، اما در عمل، بردهای حرفه‌ای معمولاً بین ۴ تا ۷ ستون دارند. در ادامه، یک نمونه برد کامل با ۴ ستون نمایش داده شده است:</p>

<div class="kanban-board-demo">
<h4>📋 نمونه برد کانبان - پروژه توسعه نرم‌افزار</h4>
<div class="kanban-columns">
<div class="kanban-column col-todo">
<div class="kanban-column-header">
📥 To Do
<span class="count">3</span>
</div>
<div class="kanban-card normal">
<div class="card-title">طراحی صفحه ورود</div>
<div class="card-meta">
<span>👤 علی</span>
<span>📅 ۲۵ شهریور</span>
</div>
<span class="card-tag">UI/UX</span>
</div>
<div class="kanban-card urgent">
<div class="card-title">رفع باگ پرداخت</div>
<div class="card-meta">
<span>👤 سارا</span>
<span>📅 ۲۳ شهریور</span>
</div>
<span class="card-tag">Bug</span>
</div>
<div class="kanban-card normal">
<div class="card-title">نوشتن مستندات API</div>
<div class="card-meta">
<span>👤 رضا</span>
<span>📅 ۲۸ شهریور</span>
</div>
<span class="card-tag">Docs</span>
</div>
</div>

<div class="kanban-column col-doing">
<div class="kanban-column-header">
🔨 In Progress
<span class="count">2</span>
</div>
<div class="kanban-card urgent">
<div class="card-title">پیاده‌سازی احراز هویت</div>
<div class="card-meta">
<span>👤 محمد</span>
<span>📅 ۲۴ شهریور</span>
</div>
<span class="card-tag">Backend</span>
</div>
<div class="kanban-card normal">
<div class="card-title">توسعه داشبورد مدیر</div>
<div class="card-meta">
<span>👤 مریم</span>
<span>📅 ۳۰ شهریور</span>
</div>
<span class="card-tag">Feature</span>
</div>
</div>

<div class="kanban-column col-review">
<div class="kanban-column-header">
🔍 Code Review
<span class="count">1</span>
</div>
<div class="kanban-card normal">
<div class="card-title">بهینه‌سازی کوئری‌ها</div>
<div class="card-meta">
<span>👤 حسین</span>
<span>📅 ۲۲ شهریور</span>
</div>
<span class="card-tag">Performance</span>
</div>
</div>

<div class="kanban-column col-done">
<div class="kanban-column-header">
✅ Done
<span class="count">2</span>
</div>
<div class="kanban-card normal">
<div class="card-title">راه‌اندازی CI/CD</div>
<div class="card-meta">
<span>👤 علی</span>
<span>📅 ۲۰ شهریور</span>
</div>
<span class="card-tag">DevOps</span>
</div>
<div class="kanban-card normal">
<div class="card-title">طراحی دیتابیس</div>
<div class="card-meta">
<span>👤 سارا</span>
<span>📅 ۱۸ شهریور</span>
</div>
<span class="card-tag">Database</span>
</div>
</div>
</div>
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">ستون‌های پیشنهادی برای حوزه‌های مختلف:</h4>

<table class="styled-table">
<thead>
<tr>
<th>حوزه</th>
<th>ستون‌های پیشنهادی</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>توسعه نرم‌افزار</strong></td>
<td>Backlog → To Do → In Progress → Code Review → Testing → Done</td>
</tr>
<tr>
<td><strong>بازاریابی محتوا</strong></td>
<td>Ideas → Research → Drafting → Editing → Publishing → Promoting</td>
</tr>
<tr>
<td><strong>منابع انسانی</strong></td>
<td>Open Positions → Screening → Interview → Offer → Onboarding → Hired</td>
</tr>
<tr>
<td><strong>پشتیبانی مشتریان</strong></td>
<td>New Tickets → Investigating → Waiting Customer → Resolving → Resolved</td>
</tr>
<tr>
<td><strong>مدیریت شخصی</strong></td>
<td>Backlog → This Week → Today → Doing → Done</td>
</tr>
</tbody>
</table>

<div class="info-box">
<strong>💡 نکته طراحی:</strong> تعداد ستون‌ها را بر اساس پیچیدگی واقعی فرآیند خود تنظیم کنید. ستون‌های اضافی، سربار مدیریتی ایجاد می‌کنند و ستون‌های ناکافی، شفافیت را کاهش می‌دهند. قانون کلی: «به تعداد کافی، نه بیشتر».
</div>

</div>
</div>

<!-- ═══════════════ بخش ۵: استفاده عملی ═══════════════ -->
<div class="section">
<div class="section-header">
<div class="section-number">۵</div>
<div class="section-title">
<h3>نحوه استفاده عملی از بردها برای مدیریت وظایف</h3>
<div class="subtitle">Practical Usage for Task Management</div>
</div>
</div>
<div class="section-content">

<p>استفاده مؤثر از بردهای کانبان، نیازمند رعایت یک گردش کار (Workflow) مشخص است. در ادامه، گام‌به‌گام نحوه استفاده از برد کانبان برای مدیریت وظایف تشریح شده است:</p>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">گام ۱: تعریف فرآیند کار</h4>
<p class="no-indent">قبل از هر چیز، باید مراحل واقعی انجام یک وظیفه در تیم خود را شناسایی کنید. این مراحل، ستون‌های برد شما را تشکیل می‌دهند.</p>
<ul style="padding-right: 20px; margin-top: 10px;">
<li style="margin-bottom: 8px;">یک وظیفه از کجا شروع می‌شود؟</li>
<li style="margin-bottom: 8px;">از چه مراحلی عبور می‌کند؟</li>
<li style="margin-bottom: 8px;">چه زمانی «تکمیل‌شده» محسوب می‌شود؟</li>
</ul>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">گام ۲: ایجاد کارت‌ها</h4>
<p class="no-indent">هر وظیفه را به‌صورت یک کارت جداگانه وارد برد کنید. کارت باید:</p>
<ul style="padding-right: 20px; margin-top: 10px;">
<li style="margin-bottom: 8px;">عنوانی واضح و قابل فهم داشته باشد</li>
<li style="margin-bottom: 8px;">به یک نفر (یا تیم مشخص) تخصیص داده شود</li>
<li style="margin-bottom: 8px;">دارای تاریخ سررسید باشد</li>
<li style="margin-bottom: 8px;">با برچسب مناسب دسته‌بندی شود</li>
</ul>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">گام ۳: تعیین محدودیت WIP</h4>
<p class="no-indent">برای هر ستون میانی (غیر از ورودی و خروجی)، یک محدودیت WIP تعیین کنید. فرمول پیشنهادی:</p>
<div class="highlight-box">
<strong>📐 فرمول WIP Limit:</strong><br>
WIP Limit هر ستون = (تعداد اعضای تیم × ۱.۵) ÷ (تعداد ستون‌های میانی)<br><br>
<strong>مثال:</strong> تیم ۴ نفره با ۳ ستون میانی → WIP = (۴ × ۱.۵) ÷ ۳ = <strong>۲ کارت برای هر ستون</strong>
</div>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">گام ۴: شروع کار با قانون Pull</h4>
<p class="no-indent">در کانبان، کار «Push» نمی‌شود، بلکه «Pull» می‌شود. این یعنی:</p>
<ul style="padding-right: 20px; margin-top: 10px;">
<li style="margin-bottom: 8px;">تا زمانی که در ستون فعلی ظرفیت خالی ندارید، کارت جدیدی شروع نکنید</li>
<li style="margin-bottom: 8px;">وقتی کاری را تمام کردید، از ستون قبلی یک کارت بکشید (Pull)</li>
<li style="margin-bottom: 8px;">هرگز از ظرفیت WIP بیشتر نشود</li>
</ul>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">گام ۵: جلسات روزانه (Daily Standup)</h4>
<p class="no-indent">هر روز، یک جلسه ۱۵ دقیقه‌ای جلوی برد برگزار کنید و به سه سؤال پاسخ دهید:</p>
<ul style="padding-right: 20px; margin-top: 10px;">
<li style="margin-bottom: 8px;"><strong>چه چیزهایی به سمت تکمیل حرکت می‌کنند؟</strong> (تمرکز بر تکمیل، نه شروع)</li>
<li style="margin-bottom: 8px;"><strong>چه چیزهایی مسدود شده‌اند؟</strong> (شناسایی گلوگاه‌ها)</li>
<li style="margin-bottom: 8px;"><strong>چه کارهای جدیدی باید شروع شوند؟</strong> (تنها اگر ظرفیت وجود دارد)</li>
</ul>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">گام ۶: بازبینی و بهبود مستمر</h4>
<p class="no-indent">هر هفته یا هر Sprint، برد را بازبینی کنید:</p>
<ul style="padding-right: 20px; margin-top: 10px;">
<li style="margin-bottom: 8px;">آیا WIP Limits مناسب هستند؟</li>
<li style="margin-bottom: 8px;">کدام ستون‌ها گلوگاه شده‌اند؟</li>
<li style="margin-bottom: 8px;">آیا نیاز به افزودن یا حذف ستون داریم؟</li>
<li style="margin-bottom: 8px;">میانگین Cycle Time چقدر است؟</li>
</ul>

<div class="success-box">
<strong>✓ گردش کار کامل یک کارت:</strong><br>
۱. ایجاد کارت در ستون Backlog → ۲. انتقال به To Do (برنامه‌ریزی) → ۳. Pull به In Progress (شروع کار) → ۴. انتقال به Review (بررسی) → ۵. انتقال به Done (تکمیل) → ۶. ثبت در تاریخچه
</div>

</div>
</div>

<!-- ═══════════════ بخش ۶: بهترین روش‌ها ═══════════════ -->
<div class="section">
<div class="section-header">
<div class="section-number">۶</div>
<div class="section-title">
<h3>بهترین روش‌ها (Best Practices)</h3>
<div class="subtitle">Best Practices for Kanban Success</div>
</div>
</div>
<div class="section-content">

<p>بر اساس تجربیات جهانی و تحقیقات صنعتی، رعایت موارد زیر، موفقیت پیاده‌سازی کانبان را به‌طور چشمگیری افزایش می‌دهد:</p>

<div class="principle-grid">
<div class="principle-card">
<div class="principle-icon">🎯</div>
<h5>کارت‌ها را کوچک نگه دارید</h5>
<p>هر کارت باید در کمتر از ۲-۳ روز قابل تکمیل باشد. اگر کاری بزرگ است، آن را به چند کارت کوچکتر تقسیم کنید.</p>
</div>
<div class="principle-card">
<div class="principle-icon">📝</div>
<h5>Definition of Done (DoD) مشخص</h5>
<p>برای هر ستون، معیارهای مشخصی تعریف کنید که کارت چه زمانی مجاز به انتقال به ستون بعدی است.</p>
</div>
<div class="principle-card">
<div class="principle-icon">🚫</div>
<h5>کارت‌های مسدود را برجسته کنید</h5>
<p>کارت‌هایی که منتظر پاسخ یا منبع خارجی هستند را با رنگ قرمز مشخص کنید تا گلوگاه‌ها دیده شوند.</p>
</div>
<div class="principle-card">
<div class="principle-icon">📊</div>
<h5>معیارها را ردیابی کنید</h5>
<p>Lead Time، Cycle Time و Throughput را اندازه‌گیری کنید تا بتوانید بهبودها را کمی‌سازی کنید.</p>
</div>
<div class="principle-card">
<div class="principle-icon">🔄</div>
<h5>بازبینی منظم فرآیند</h5>
<p>هر ۲ هفته یک‌بار، فرآیند را بازبینی و در صورت نیاز، ستون‌ها یا WIP Limits را تنظیم کنید.</p>
</div>
<div class="principle-card">
<div class="principle-icon">👥</div>
<h5>شفافیت کامل برای همه</h5>
<p>برد باید برای تمام اعضای تیم و ذینفعان قابل مشاهده باشد. هیچ «کاری پنهانی» نباید وجود داشته باشد.</p>
</div>
</div>

<div class="highlight-box">
<strong>🎯 قانون طلایی:</strong> «اگر روی برد نیست، وجود ندارد.» هر کاری که انجام می‌دهید باید روی برد قابل مشاهده باشد. کارهای پنهان، دشمن شفافیت و بهبود مستمر هستند.
</div>

</div>
</div>

<!-- ═══════════════ بخش ۷: اشتباهات رایج ═══════════════ -->
<div class="section">
<div class="section-header">
<div class="section-number">۷</div>
<div class="section-title">
<h3>اشتباهات رایج و راه‌حل‌ها</h3>
<div class="subtitle">Common Mistakes & Solutions</div>
</div>
</div>
<div class="section-content">

<p>بسیاری از تیم‌ها در پیاده‌سازی کانبان با چالش‌هایی مواجه می‌شوند. شناخت این اشتباهات رایج و راه‌حل‌های آن‌ها، از اتلاف زمان و انرژی جلوگیری می‌کند:</p>

<div class="warning-box">
<strong>❌ اشتباه ۱: نادیده گرفتن محدودیت WIP</strong>
<p style="margin-top: 10px;">بسیاری از تیم‌ها WIP Limit را تعیین می‌کنند اما به آن پایبند نمی‌مانند. نتیجه: چندوظیفه‌ای بودن، کاهش تمرکز و افزایش زمان تکمیل.</p>
<p style="margin-top: 8px;"><strong>✓ راه‌حل:</strong> WIP Limit را به‌صورت بصری روی برد نمایش دهید و در جلسات روزانه، رعایت آن را بررسی کنید.</p>
</div>

<div class="warning-box">
<strong>❌ اشتباه ۲: کارت‌های بیش‌ازحد بزرگ</strong>
<p style="margin-top: 10px;">کارت‌هایی که هفته‌ها طول می‌کشد تا تکمیل شوند، جریان کار را مختل می‌کنند و شفافیت را از بین می‌برند.</p>
<p style="margin-top: 8px;"><strong>✓ راه‌حل:</strong> هر کاری که بیش از ۳ روز طول می‌کشد را به چند کارت کوچکتر تقسیم کنید.</p>
</div>

<div class="warning-box">
<strong>❌ اشتباه ۳: ستون‌های بیش‌ازحد</strong>
<p style="margin-top: 10px;">ایجاد ۱۰+ ستون، سربار مدیریتی ایجاد می‌کند و تیم را در جابجایی کارت‌ها غرق می‌سازد.</p>
<p style="margin-top: 8px;"><strong>✓ راه‌حل:</strong> با ۴-۵ ستون شروع کنید و تنها در صورت نیاز واقعی، ستون جدید اضافه کنید.</p>
</div>

<div class="warning-box">
<strong>❌ اشتباه ۴: عدم به‌روزرسانی منظم برد</strong>
<p style="margin-top: 10px;">اگر برد به‌روز نباشد، به سرعت اعتماد تیم به آن از بین می‌رود و برد به یک «تابلوی تزئینی» تبدیل می‌شود.</p>
<p style="margin-top: 8px;"><strong>✓ راه‌حل:</strong> قانون بگذارید: «هر تغییر وضعیت، بلافاصله روی برد منعکس شود».</p>
</div>

<div class="warning-box">
<strong>❌ اشتباه ۵: نادیده گرفتن کارت‌های مسدود</strong>
<p style="margin-top: 10px;">کارت‌هایی که هفته‌ها در یک ستون می‌مانند، نشان‌دهنده گلوگاه‌های جدی در فرآیند هستند.</p>
<p style="margin-top: 8px;"><strong>✓ راه‌حل:</strong> هر کارت مسدود بیش از ۲ روز را در جلسات روزانه بررسی و رفع کنید.</p>
</div>

<div class="warning-box">
<strong>❌ اشتباه ۶: استفاده از کانبان بدون درک فلسفه</strong>
<p style="margin-top: 10px;">برخی تیم‌ها کانبان را صرفاً به‌عنوان یک «ابزار بصری» می‌بینند و اصول آن را نادیده می‌گیرند.</p>
<p style="margin-top: 8px;"><strong>✓ راه‌حل:</strong> قبل از استفاده از ابزار، فلسفه و اصول کانبان را به‌طور کامل درک کنید.</p>
</div>

</div>
</div>

<!-- ═══════════════ بخش ۸: معیارها ═══════════════ -->
<div class="section">
<div class="section-header">
<div class="section-number">۸</div>
<div class="section-title">
<h3>معیارهای اندازه‌گیری عملکرد</h3>
<div class="subtitle">Performance Metrics & Measurement</div>
</div>
</div>
<div class="section-content">

<p>برای اینکه بدانید آیا کانبان در تیم شما کار می‌کند یا خیر، باید معیارهای کلیدی را اندازه‌گیری کنید. این معیارها، زبان مشترک تیم برای گفتگو درباره بهبود فرآیند هستند.</p>

<div class="metric-card">
<div class="metric-icon">⏱️</div>
<div class="metric-body">
<h5>۱. Lead Time (زمان انتظار)</h5>
<p>مدت زمانی که یک کارت از لحظه ایجاد تا لحظه تکمیل در سیستم بوده است. این معیار، سرعت کلی سیستم را نشان می‌دهد.</p>
<div class="formula">Lead Time = Completion Date - Creation Date</div>
</div>
</div>

<div class="metric-card">
<div class="metric-icon">🔄</div>
<div class="metric-body">
<h5>۲. Cycle Time (زمان چرخه)</h5>
<p>مدت زمانی که یک کارت از لحظه شروع کار (In Progress) تا تکمیل طول کشیده است. این معیار، بهره‌وری واقعی تیم را نشان می‌دهد.</p>
<div class="formula">Cycle Time = Completion Date - Start Date</div>
</div>
</div>

<div class="metric-card">
<div class="metric-icon">📊</div>
<div class="metric-body">
<h5>۳. Throughput (نرخ خروجی)</h5>
<p>تعداد کارت‌های تکمیل‌شده در یک بازه زمانی مشخص (مثلاً در هفته یا ماه). این معیار، ظرفیت واقعی تیم را نشان می‌دهد.</p>
<div class="formula">Throughput = Completed Cards / Time Period</div>
</div>
</div>

<div class="metric-card">
<div class="metric-icon">📈</div>
<div class="metric-body">
<h5>۴. WIP (کار در جریان)</h5>
<p>تعداد کارت‌هایی که در حال حاضر در ستون‌های میانی هستند. این معیار، بار کاری فعلی تیم را نشان می‌دهد.</p>
<div class="formula">WIP = Cards in all WIP columns</div>
</div>
</div>

<div class="metric-card">
<div class="metric-icon">⚠️</div>
<div class="metric-body">
<h5>۵. Blocked Ratio (نرخ مسدودی)</h5>
<p>درصد کارت‌هایی که در وضعیت مسدود هستند. این معیار، سلامت فرآیند را نشان می‌دهد.</p>
<div class="formula">Blocked Ratio = (Blocked Cards / Total Cards) × 100</div>
</div>
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">نمودارهای کانبان:</h4>
<table class="styled-table">
<thead>
<tr>
<th>نمودار</th>
<th>هدف</th>
<th>کاربرد</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Cumulative Flow Diagram (CFD)</strong></td>
<td>نمایش جریان تجمعی کارت‌ها در طول زمان</td>
<td>شناسایی گلوگاه‌ها و پیش‌بینی تکمیل</td>
</tr>
<tr>
<td><strong>Control Chart</strong></td>
<td>نمایش Cycle Time هر کارت</td>
<td>شناسایی ناهنجاری‌ها و بهبود پیش‌بینی</td>
</tr>
<tr>
<td><strong>Throughput Histogram</strong></td>
<td>نمایش توزیع تعداد کارت‌های تکمیل‌شده</td>
<td>درک ظرفیت واقعی تیم</td>
</tr>
<tr>
<td><strong>Aging Chart</strong></td>
<td>نمایش سن کارت‌های در جریان</td>
<td>شناسایی کارت‌های قدیمی و نیازمند توجه</td>
</tr>
</tbody>
</table>

<div class="info-box">
<strong>💡 قانون اندازه‌گیری:</strong> «آنچه اندازه‌گیری نمی‌شود، مدیریت نمی‌شود.» حداقل یکی از معیارهای بالا را به‌صورت منظم ردیابی کنید تا بتوانید بهبودها را کمی‌سازی کنید.
</div>

</div>
</div>

<!-- ═══════════════ بخش ۹: مثال‌های کاربردی ═══════════════ -->
<div class="section">
<div class="section-header">
<div class="section-number">۹</div>
<div class="section-title">
<h3>مثال‌های کاربردی در حوزه‌های مختلف</h3>
<div class="subtitle">Practical Examples Across Domains</div>
</div>
</div>
<div class="section-content">

<p>کانبان یک متدولوژی جهانی است و در حوزه‌های بسیار متنوعی کاربرد دارد. در ادامه، چند مثال کاربردی از پیاده‌سازی کانبان در حوزه‌های مختلف ارائه شده است:</p>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">مثال ۱: تیم توسعه نرم‌افزار</h4>
<p class="no-indent"><strong>چالش:</strong> تیم ۵ نفره با بیش از ۲۰ کار هم‌زمان، که هیچ‌کدام به موقع تکمیل نمی‌شوند.</p>
<p class="no-indent"><strong>راه‌حل کانبان:</strong></p>
<ul style="padding-right: 20px; margin-top: 10px;">
<li style="margin-bottom: 8px;">ایجاد برد با ۶ ستون: Backlog → To Do → In Progress → Code Review → Testing → Done</li>
<li style="margin-bottom: 8px;">تعیین WIP Limit: ۲ کارت برای هر ستون میانی</li>
<li style="margin-bottom: 8px;">جلسات روزانه ۱۵ دقیقه‌ای جلوی برد</li>
<li style="margin-bottom: 8px;"><strong>نتیجه:</strong> کاهش Cycle Time از ۱۴ روز به ۵ روز در عرض ۲ ماه</li>
</ul>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">مثال ۲: تیم بازاریابی محتوا</h4>
<p class="no-indent"><strong>چالش:</strong> تولید محتوای نامنظم، فراموش شدن ایده‌ها، عدم هماهنگی بین نویسندگان و ویراستاران.</p>
<p class="no-indent"><strong>راه‌حل کانبان:</strong></p>
<ul style="padding-right: 20px; margin-top: 10px;">
<li style="margin-bottom: 8px;">ایجاد برد با ۵ ستون: Ideas → Research → Drafting → Editing → Published</li>
<li style="margin-bottom: 8px;">استفاده از برچسب‌های رنگی برای نوع محتوا (Blog، Video، Social)</li>
<li style="margin-bottom: 8px"><strong>نتیجه:</strong> افزایش تولید محتوا از ۴ مقاله در ماه به ۱۲ مقاله با کیفیت بالاتر</li>
</ul>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">مثال ۳: مدیریت شخصی</h4>
<p class="no-indent"><strong>چالش:</strong> احساس سردرگمی، فراموش کردن وظایف، عدم پیشرفت در اهداف شخصی.</p>
<p class="no-indent"><strong>راه‌حل کانبان:</strong></p>
<ul style="padding-right: 20px; margin-top: 10px;">
<li style="margin-bottom: 8px;">ایجاد برد شخصی با ۴ ستون: Backlog → This Week → Today → Done</li>
<li style="margin-bottom: 8px;">WIP Limit: حداکثر ۳ کار برای «Today»</li>
<li style="margin-bottom: 8px;">بازبینی هفتگی هر جمعه evening</li>
<li style="margin-bottom: 8px;"><strong>نتیجه:</strong> افزایش بهره‌وری شخصی و کاهش استرس به‌طور چشمگیر</li>
</ul>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">مثال ۴: تیم پشتیبانی مشتریان</h4>
<p class="no-indent"><strong>چالش:</strong> تیکت‌های انباشته‌شده، عدم شفافیت در وضعیت درخواست‌ها، پاسخگویی کند.</p>
<p class="no-indent"><strong>راه‌حل کانبان:</strong></p>
<ul style="padding-right: 20px; margin-top: 10px;">
<li style="margin-bottom: 8px;">ایجاد برد با ۵ ستون: New → Investigating → Waiting Customer → Resolving → Resolved</li>
<li style="margin-bottom: 8px;">اولویت‌بندی با برچسب‌های رنگی (Critical، High، Normal، Low)</li>
<li style="margin-bottom: 8px;"><strong>نتیجه:</strong> کاهش میانگین زمان پاسخ از ۴۸ ساعت به ۸ ساعت</li>
</ul>

<div class="success-box">
<strong>💎 درس مشترک:</strong> در تمام این مثال‌ها، کانبان با ایجاد <strong>شفافیت بصری</strong>، <strong>محدود کردن کار در جریان</strong> و <strong>تمرکز بر تکمیل به جای شروع</strong>، بهره‌وری را به‌طور چشمگیری افزایش داده است.
</div>

</div>
</div>

<!-- ═══════════════ بخش ۱۰: جمع‌بندی ═══════════════ -->
<div class="section">
<div class="section-header">
<div class="section-number">۱۰</div>
<div class="section-title">
<h3>جمع‌بندی و گام‌های بعدی</h3>
<div class="subtitle">Summary & Next Steps</div>
</div>
</div>
<div class="section-content">

<p>کانبان یک متدولوژی قدرتمند، انعطاف‌پذیر و اثبات‌شده برای مدیریت بصری کارهاست. این متدولوژی با تأکید بر شفافیت، محدود کردن کار در جریان و بهبود مستمر، به تیم‌ها کمک می‌کند تا با تمرکز بیشتر، استرس کمتر و بهره‌وری بالاتر کار کنند.</p>

<div class="conclusion-box">
<h3>🎯 پیام اصلی کانبان</h3>
<p>«کار را شروع نکنید تا تمام شود، کار را تمام کنید تا شروع کنید.»</p>
<p>کانبان به شما نمی‌گوید «چه کاری انجام دهید»، بلکه به شما کمک می‌کند «بهترین روش انجام کار» را کشف کنید. این متدولوژی، ابزاری برای بهبود مستمر است، نه یک چارچوب خشک و غیرقابل تغییر.</p>
<div class="highlight">
<strong>💎 سه اصل کلیدی برای موفقیت:</strong><br>
۱. شفاف‌سازی فرآیند فعلی &nbsp;|&nbsp; ۲. محدود کردن کار در جریان &nbsp;|&nbsp; ۳. بهبود مستمر و تدریجی
</div>
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">گام‌های پیشنهادی برای شروع:</h4>
<table class="styled-table">
<thead>
<tr>
<th>گام</th>
<th>اقدام</th>
<th>زمان تخمینی</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>۱</strong></td>
<td>فرآیند فعلی تیم خود را روی کاغذ بنویسید</td>
<td>۳۰ دقیقه</td>
</tr>
<tr>
<td><strong>۲</strong></td>
<td>یک برد کانبان ساده با ۴ ستون ایجاد کنید</td>
<td>۱۵ دقیقه</td>
</tr>
<tr>
<td><strong>۳</strong></td>
<td>تمام وظایف فعلی را روی کارت‌های جداگانه وارد کنید</td>
<td>۱ ساعت</td>
</tr>
<tr>
<td><strong>۴</strong></td>
<td>برای ستون‌های میانی، WIP Limit تعیین کنید</td>
<td>۱۰ دقیقه</td>
</tr>
<tr>
<td><strong>۵</strong></td>
<td>جلسات روزانه ۱۵ دقیقه‌ای را شروع کنید</td>
<td>هر روز</td>
</tr>
<tr>
<td><strong>۶</strong></td>
<td>پس از ۲ هفته، فرآیند را بازبینی و بهبود دهید</td>
<td>۱ ساعت</td>
</tr>
</tbody>
</table>

<div class="info-box">
<strong>📞 منابع بیشتر:</strong> برای یادگیری عمیق‌تر کانبان، کتاب‌های زیر پیشنهاد می‌شوند:
<ul class="reference-list" style="margin-top: 15px;">
<li>
<strong>Kanban: Successful Evolutionary Change for Your Technology Business</strong>
<div class="ref-source">نویسنده: David J. Anderson</div>
</li>
<li>
<strong>Kanban from the Inside</strong>
<div class="ref-source">نویسنده: Mike Burrows</div>
</li>
<li>
<strong>Personal Kanban</strong>
<div class="ref-source">نویسندگان: Jim Benson & Tonianne DeMaria Barry</div>
</li>
</ul>
</div>

<div class="info-box">
<strong>🛠️ ابزار پیشنهادی:</strong> برای شروع سریع و بدون نیاز به نصب یا راه‌اندازی، ابزار <strong>xKanban</strong> (طراحی‌شده توسط هادی خزاعی اصل) یک گزینه ایده‌آل است. این ابزار به‌صورت یک فایل HTML مستقل، تمامی قابلیت‌های یک برد کانبان حرفه‌ای را به‌صورت آفلاین و رایگان ارائه می‌دهد.
</div>

</div>
</div>

<div class="page-info">
این مستند آموزشی جهت استفاده عمومی تهیه شده است. بازنشر با ذکر منبع مجاز است.
</div>

</div>

<!-- ═══════════════ FOOTER ═══════════════ -->
<footer class="document-footer">
<div class="footer-brand">طراحی، تحلیل و توسعه: هادی خزاعی اصل</div>
<div style="font-size: 11px; opacity: 0.8; margin-bottom: 5px;">معمار و توسعه‌دهنده ارشد نرم‌افزار | متخصص .NET و Angular</div>
<div class="footer-divider"></div>
<div>تاریخ تهیه: یکشنبه ۲۲ شهریور ۱۴۰۵ (۱۳ سپتامبر ۲۰۲۶) &nbsp;|&nbsp; ساعت: ۱۱:۰۰ &nbsp;|&nbsp; نسخه: ۱.۰.۰ &nbsp;|&nbsp; طبقه‌بندی: عمومی - آموزشی</div>
</footer>

</div>
</body>
</html>

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Resources\Tools\Document\xUMLDrawer_Case_Study.html

<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>معرفی ابزار رسم نمودارهای UML | تحلیل نیازمندی‌ها، معماری و ارزش‌های سازمانی</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap');
:root {
--primary: #1a365d;
--primary-light: #2c5282;
--accent: #c9a961;
--accent-dark: #a8893f;
--bg: #faf9f6;
--card-bg: #ffffff;
--text: #1a202c;
--text-muted: #4a5568;
--border: #e2e8f0;
--success: #38a169;
--danger: #e53e3e;
--warning: #dd6b20;
--info: #3182ce;
--shadow: 0 4px 6px rgba(0,0,0,0.05), 0 10px 30px rgba(26,54,93,0.08);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Vazirmatn', Tahoma, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.9;
font-size: 15px;
padding: 30px 15px;
}
.document-container {
max-width: 950px;
margin: 0 auto;
background: var(--card-bg);
box-shadow: var(--shadow);
border-radius: 12px;
overflow: hidden;
}
.document-header {
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);
color: white;
padding: 50px 40px 40px;
position: relative;
border-bottom: 4px solid var(--accent);
}
.document-header::before {
content: '';
position: absolute;
top: 0; right: 0;
width: 200px; height: 200px;
background: radial-gradient(circle, rgba(201,169,97,0.15) 0%, transparent 70%);
border-radius: 50%;
transform: translate(50%, -50%);
}
.header-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
position: relative;
z-index: 1;
}
.company-badge {
display: flex;
align-items: center;
gap: 12px;
}
.company-logo {
width: 60px; height: 60px;
background: var(--accent);
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
.doc-meta {
text-align: left;
font-size: 12px;
opacity: 0.9;
}
.doc-meta div { margin-bottom: 3px; }
.document-title {
text-align: center;
position: relative;
z-index: 1;
padding: 20px 0;
}
.document-title .label {
display: inline-block;
background: rgba(201,169,97,0.2);
color: var(--accent);
padding: 4px 16px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
margin-bottom: 15px;
border: 1px solid rgba(201,169,97,0.4);
}
.document-title h1 {
font-size: 28px;
font-weight: 800;
margin-bottom: 10px;
letter-spacing: -0.5px;
}
.document-title h2 {
font-size: 16px;
font-weight: 400;
opacity: 0.9;
}
.document-body {
padding: 40px;
}
.intro-text {
background: linear-gradient(to left, #f7fafc, #edf2f7);
border-right: 4px solid var(--accent);
padding: 20px 25px;
border-radius: 8px;
margin-bottom: 35px;
font-size: 14px;
color: var(--text-muted);
}
.section {
margin-bottom: 40px;
page-break-inside: avoid;
}
.section-header {
display: flex;
align-items: center;
gap: 15px;
padding-bottom: 12px;
border-bottom: 2px solid var(--border);
margin-bottom: 20px;
}
.section-number {
width: 50px; height: 50px;
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 800;
font-size: 20px;
flex-shrink: 0;
box-shadow: 0 4px 10px rgba(26,54,93,0.2);
}
.section-title { flex: 1; }
.section-title h3 {
font-size: 19px;
font-weight: 700;
color: var(--primary);
margin-bottom: 2px;
}
.section-title .subtitle {
font-size: 12px;
color: var(--text-muted);
}
.section-content {
padding-right: 10px;
}
.section-content p {
margin-bottom: 12px;
text-align: justify;
text-indent: 20px;
}
.section-content p.no-indent { text-indent: 0; }
.highlight-box {
background: linear-gradient(to left, #fffaf0, #fef5e7);
border: 1px solid #f6e05e;
border-right: 4px solid var(--accent);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.highlight-box strong {
color: var(--accent-dark);
}
.info-box {
background: linear-gradient(to left, #ebf8ff, #e6f6ff);
border: 1px solid #90cdf4;
border-right: 4px solid var(--primary-light);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.success-box {
background: linear-gradient(to left, #f0fff4, #e6fffa);
border: 1px solid #9ae6b4;
border-right: 4px solid var(--success);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.warning-box {
background: linear-gradient(to left, #fffaf0, #fef5e7);
border: 1px solid #fbd38d;
border-right: 4px solid var(--warning);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.danger-box {
background: linear-gradient(to left, #fff5f5, #fed7d7);
border: 1px solid #feb2b2;
border-right: 4px solid var(--danger);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.styled-table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
}
.styled-table thead {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
}
.styled-table th {
padding: 14px 16px;
text-align: right;
font-weight: 600;
font-size: 13px;
}
.styled-table td {
padding: 13px 16px;
border-bottom: 1px solid var(--border);
font-size: 13px;
}
.styled-table tbody tr:last-child td { border-bottom: none; }
.styled-table tbody tr:nth-child(even) { background: #f8fafc; }
.styled-table .highlight-row {
background: #fffaf0 !important;
font-weight: 600;
}
.challenge-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
margin: 25px 0;
}
.challenge-card {
background: #f8fafc;
border: 2px solid var(--border);
border-radius: 10px;
padding: 18px;
transition: all 0.2s;
page-break-inside: avoid;
}
.challenge-card:hover {
border-color: var(--warning);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(221,107,32,0.1);
}
.challenge-card .icon {
font-size: 28px;
margin-bottom: 8px;
}
.challenge-card h4 {
font-size: 15px;
font-weight: 700;
color: var(--primary);
margin-bottom: 6px;
}
.challenge-card p {
font-size: 12px;
color: var(--text-muted);
text-indent: 0 !important;
margin-bottom: 0 !important;
}
.value-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
margin: 25px 0;
}
.value-card {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
padding: 20px;
border-radius: 10px;
text-align: center;
box-shadow: 0 4px 12px rgba(26,54,93,0.2);
page-break-inside: avoid;
}
.value-card .icon {
font-size: 32px;
margin-bottom: 10px;
}
.value-card h4 {
font-size: 14px;
font-weight: 700;
margin-bottom: 8px;
color: var(--accent);
}
.value-card p {
font-size: 12px;
opacity: 0.95;
text-indent: 0 !important;
margin-bottom: 0 !important;
line-height: 1.7;
}
.audience-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
margin: 25px 0;
}
.audience-card {
background: #f8fafc;
border: 2px solid var(--border);
border-radius: 10px;
padding: 18px;
page-break-inside: avoid;
}
.audience-card .icon {
font-size: 28px;
margin-bottom: 8px;
}
.audience-card h4 {
font-size: 15px;
font-weight: 700;
color: var(--primary);
margin-bottom: 6px;
}
.audience-card p {
font-size: 12px;
color: var(--text-muted);
text-indent: 0 !important;
margin-bottom: 0 !important;
}
.code-block {
background: #1a202c;
color: #e2e8f0;
padding: 18px 22px;
border-radius: 8px;
margin: 15px 0;
font-family: 'Consolas', 'Courier New', monospace;
font-size: 13px;
line-height: 1.7;
direction: ltr;
text-align: left;
overflow-x: auto;
border-right: 4px solid var(--accent);
}
.code-block .keyword { color: #f6ad55; }
.code-block .string { color: #9ae6b4; }
.code-block .comment { color: #718096; font-style: italic; }
.code-block .func { color: #90cdf4; }
.code-block .ellipsis { color: #a0aec0; font-style: italic; }
.module-list {
list-style: none;
padding: 0;
margin: 15px 0;
}
.module-list li {
background: #f8fafc;
border-right: 3px solid var(--primary-light);
padding: 12px 18px;
margin-bottom: 10px;
border-radius: 6px;
font-size: 13px;
}
.module-list li strong {
color: var(--primary);
display: inline-block;
margin-left: 8px;
}
.arch-layer {
background: linear-gradient(to left, #f7fafc, #edf2f7);
border: 1px solid var(--border);
border-right: 4px solid var(--primary-light);
padding: 18px 22px;
border-radius: 8px;
margin: 15px 0;
}
.arch-layer h4 {
color: var(--primary);
font-size: 15px;
font-weight: 700;
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 8px;
}
.arch-layer ul {
padding-right: 20px;
margin-top: 8px;
}
.arch-layer li {
margin-bottom: 6px;
font-size: 13px;
}
.conclusion-box {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
padding: 30px;
border-radius: 12px;
margin: 30px 0;
text-align: center;
box-shadow: 0 8px 20px rgba(26,54,93,0.3);
}
.conclusion-box h3 {
font-size: 22px;
font-weight: 800;
margin-bottom: 15px;
color: var(--accent);
}
.conclusion-box p {
font-size: 14px;
line-height: 1.8;
text-indent: 0 !important;
margin-bottom: 15px;
}
.conclusion-box .highlight {
background: rgba(201,169,97,0.2);
padding: 15px 20px;
border-radius: 8px;
margin-top: 20px;
border: 1px solid rgba(201,169,97,0.4);
}
.document-footer {
background: var(--primary);
color: white;
padding: 25px 40px;
text-align: center;
font-size: 12px;
margin-top: 40px;
}
.document-footer .footer-brand {
font-weight: 700;
color: var(--accent);
margin-bottom: 5px;
font-size: 14px;
}
.footer-divider {
width: 60px;
height: 2px;
background: var(--accent);
margin: 10px auto;
}
.page-info {
text-align: center;
padding: 15px;
font-size: 11px;
color: var(--text-muted);
border-top: 1px solid var(--border);
margin-top: 30px;
}
@media print {
body { background: white; padding: 0; }
.document-container { box-shadow: none; border-radius: 0; }
.section { page-break-inside: avoid; }
.challenge-card, .value-card, .audience-card { page-break-inside: avoid; }
}
@media (max-width: 768px) {
.document-body { padding: 25px 20px; }
.document-header { padding: 30px 20px; }
.challenge-grid, .audience-grid { grid-template-columns: 1fr; }
.value-grid { grid-template-columns: 1fr; }
.header-top { flex-direction: column; gap: 15px; }
.doc-meta { text-align: center; }
}
</style>
</head>
<body>
<div class="document-container">
<header class="document-header">
<div class="header-top">
<div class="company-badge">
<div class="company-logo"></div>
</div>
<div class="doc-meta">
<div>شماره مستند: UML-TOOL-OVERVIEW-1405-001</div>
<div>نسخه: ۱.۰</div>
<div>تاریخ تهیه: ۱۷ شهریور ۱۴۰۵ — ساعت ۱۰:۳۰</div>
<div>طبقه‌بندی: عمومی / معرفی محصول</div>
</div>
</div>
<div class="document-title">
<span class="label">PRODUCT OVERVIEW & ARCHITECTURE</span>
<h1>معرفی ابزار رسم نمودارهای UML</h1>
<h2>تحلیل نیازمندی‌ها، معماری فنی و ارزش‌های سازمانی</h2>
</div>
</header>
<div class="document-body">
<div class="intro-text">
<strong>همکاران گرامی و علاقه‌مندان به مهندسی نرم‌افزار،</strong><br>
این مستند به‌عنوان مرجعی جامع برای معرفی «ابزار رسم نمودارهای UML» تهیه شده است. در این مستند، چرایی پیدایش این ابزار، مزایای رقابتی، ارزش‌های سازمانی قابل خلق، معماری دقیق فنی و مخاطبان هدف آن به‌صورت شفاف تشریح می‌گردد. هدف از تهیه این مستند، ارائه دیدگاهی کامل برای تصمیم‌گیری آگاهانه جهت استفاده از این ابزار در فرآیندهای مهندسی نرم‌افزار سازمان‌هاست.
</div>

<div class="section">
<div class="section-header">
<div class="section-number">۱</div>
<div class="section-title">
<h3>شرح مسئله و چالش‌های موجود</h3>
<div class="subtitle">Problem Statement & Industry Challenges</div>
</div>
</div>
<div class="section-content">
<p>در دنیای پیچیده و به‌سرعت در حال تحول مهندسی نرم‌افزار، مدل‌سازی دقیق سیستم‌ها به یکی از ارکان حیاتی موفقیت پروژه‌ها تبدیل شده است. نمودارهای UML (Unified Modeling Language) به‌عنوان زبان استاندارد بین‌المللی برای مدل‌سازی بصری سیستم‌های نرم‌افزاری، نقشی کلیدی در انتقال مفاهیم، طراحی معماری، مستندسازی و ارتباط مؤثر بین ذینفعان ایفا می‌کنند. با این حال، ابزارهای موجود در بازار با چالش‌های متعددی مواجه‌اند که استفاده از آن‌ها را برای بسیاری از تیم‌ها دشوار یا حتی غیرممکن می‌سازد.</p>

<div class="challenge-grid">
<div class="challenge-card">
<div class="icon">💸</div>
<h4>هزینه‌های سنگین لایسنس</h4>
<p>ابزارهای تجاری معتبر مانند Enterprise Architect، Visual Paradigm و IBM Rational Rose دارای هزینه‌های لایسنس سالانه بسیار بالا هستند که برای بسیاری از سازمان‌ها، به‌ویژه استارتاپ‌ها و شرکت‌های متوسط، مقرون‌به‌صرفه نیست.</p>
</div>
<div class="challenge-card">
<div class="icon">🌐</div>
<h4>وابستگی به اینترنت و سرور</h4>
<p>بسیاری از ابزارهای مدرن مبتنی بر Cloud هستند و برای اجرا به اتصال اینترنت پایدار و سرورهای خارجی نیاز دارند. این موضوع در محیط‌هایی با محدودیت‌های شبکه، مسائل امنیتی یا قطعی اینترنت، چالش‌برانگیز است.</p>
</div>
<div class="challenge-card">
<div class="icon">🔒</div>
<h4>نگرانی‌های امنیتی و حریم داده</h4>
<p>ذخیره نمودارهای معماری حساس در سرورهای ابری شخص ثالث، ریسک‌های امنیتی جدی به همراه دارد. سازمان‌های دولتی، نظامی و مالی اغلب نمی‌توانند داده‌های معماری خود را به خارج از شبکه داخلی منتقل کنند.</p>
</div>
<div class="challenge-card">
<div class="icon">⚙️</div>
<h4>پیچیدگی و منحنی یادگیری بالا</h4>
<p>ابزارهای جامع UML معمولاً دارای رابط کاربری پیچیده، تعداد زیادی ویژگی غیرضروری و منحنی یادگیری طولانی هستند. این موضوع باعث می‌شود که استفاده از آن‌ها برای کارهای سریع و روزمره، زمان‌بر و خسته‌کننده باشد.</p>
</div>
<div class="challenge-card">
<div class="icon">📦</div>
<h4>حجم بالا و نیاز به نصب</h4>
<p>ابزارهای دسکتاپ معمولاً حجم بالایی دارند (چندین گیگابایت)، نیاز به نصب و پیکربندی دارند و روی همه سیستم‌ها قابل اجرا نیستند. این موضوع برای استفاده در محیط‌های محدود یا سیستم‌های قدیمی مشکل‌ساز است.</p>
</div>
<div class="challenge-card">
<div class="icon">🔄</div>
<h4>عدم پشتیبانی از همه نمودارها</h4>
<p>بسیاری از ابزارهای رایگان یا سبک، تنها چند نوع محدود از نمودارهای UML را پشتیبانی می‌کنند و امکان رسم کامل هشت نمودار اصلی را به‌صورت یکپارچه فراهم نمی‌آورند.</p>
</div>
<div class="challenge-card">
<div class="icon">🌍</div>
<h4>عدم پشتیبانی از زبان فارسی و RTL</h4>
<p>اکثر ابزارهای موجود، پشتیبانی مناسبی از زبان فارسی و چیدمان راست‌به‌چپ (RTL) ندارند. این موضوع برای تیم‌های ایرانی، استفاده از نمودارها در مستندات فارسی و ارائه به ذینفعان فارسی‌زبان، یک محدودیت جدی است.</p>
</div>
<div class="challenge-card">
<div class="icon">🔗</div>
<h4>عدم یکپارچگی و پراکندگی ابزارها</h4>
<p>تیم‌ها مجبورند برای رسم نمودارهای مختلف از ابزارهای متفاوت استفاده کنند که منجر به عدم یکپارچگی بصری، دشواری در نگهداری و افزایش هزینه‌های آموزشی می‌شود.</p>
</div>
</div>

<div class="danger-box">
<strong>⚠️ نتیجه چالش‌ها:</strong> این معضلات باعث می‌شود که بسیاری از تیم‌های نرم‌افزاری یا از مدل‌سازی UML صرف‌نظر کنند، یا از ابزارهای ناکارآمد استفاده نمایند که در نهایت به کاهش کیفیت طراحی، افزایش خطاهای معماری و کاهش بهره‌وری تیم منجر می‌شود.
</div>
</div>
</div>

<div class="section">
<div class="section-header">
<div class="section-number">۲</div>
<div class="section-title">
<h3>معرفی راه‌حل و مزایای رقابتی</h3>
<div class="subtitle">Solution Overview & Competitive Advantages</div>
</div>
</div>
<div class="section-content">
<p>«ابزار رسم نمودارهای UML» پاسخی جامع و حرفه‌ای به چالش‌های مطرح‌شده است. این ابزار با تکیه بر تکنولوژی‌های مدرن وب و معماری هوشمندانه، تجربه‌ای بی‌نظیر از مدل‌سازی بصری را در اختیار معماران، تحلیل‌گران و توسعه‌دهندگان قرار می‌دهد.</p>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">مزایای کلیدی محصول:</h4>

<div class="success-box">
<strong>✅ کاملاً رایگان و بدون لایسنس:</strong> این ابزار به‌صورت متن‌باز و کاملاً رایگان ارائه می‌شود. هیچ هزینه لایسنس، اشتراک یا پرداخت درون‌برنامه‌ای وجود ندارد. سازمان‌ها می‌توانند بدون هیچ محدودیتی از آن استفاده کنند.
</div>

<div class="success-box">
<strong>✅ ۱۰۰٪ آفلاین و بدون نیاز به سرور:</strong> تمام عملیات در سمت مرورگر و بدون هیچ‌گونه ارتباط با سرور خارجی انجام می‌شود. این ابزار در محیط‌های کاملاً ایزوله، شبکه‌های داخلی (Intranet) و حتی سیستم‌های بدون اینترنت قابل اجراست.
</div>

<div class="success-box">
<strong>✅ پشتیبانی کامل از ۸ نمودار اصلی UML:</strong>
<ul style="padding-right: 20px; margin-top: 10px;">
<li>Use Case Diagram (مورد کاربرد)</li>
<li>Activity Diagram (فعالیت)</li>
<li>State Machine Diagram (ماشین حالت)</li>
<li>Class Diagram (کلاس)</li>
<li>Sequence Diagram (توالی)</li>
<li>Component Diagram (کامپوننت)</li>
<li>Deployment Diagram (استقرار)</li>
<li>Swimlane Diagram (لاین شنا)</li>
</ul>
</div>

<div class="success-box">
<strong>✅ پشتیبانی کامل از زبان فارسی و RTL:</strong> تمامی رابط کاربری، برچسب‌ها، منوها و خروجی‌ها به‌صورت کامل از زبان فارسی و چیدمان راست‌به‌چپ پشتیبانی می‌کنند. فونت حرفه‌ای Vazirmatn برای تایپوگرافی فارسی به‌کار رفته است.
</div>

<div class="success-box">
<strong>✅ سبک، سریع و بدون نیاز به نصب:</strong> حجم ابزار کمتر از ۱ مگابایت است و تنها با یک فایل HTML قابل اجراست. نیازی به نصب، پیکربندی یا وابستگی خارجی ندارد. کافی است فایل را در هر مرورگر مدرنی باز کنید.
</div>

<div class="success-box">
<strong>✅ امنیت کامل داده‌ها:</strong> تمامی داده‌ها به‌صورت محلی و در قالب فایل JSON ذخیره می‌شوند. هیچ اطلاعاتی به سرور خارجی ارسال نمی‌شود. سازمان‌ها کنترل کامل بر داده‌های خود دارند.
</div>

<div class="success-box">
<strong>✅ تم تاریک و روشن حرفه‌ای:</strong> امکان سوئیچ آنی بین تم تاریک و روشن با حفظ ترجیحات در فایل پروژه. این ویژگی برای کار طولانی‌مدت و کاهش خستگی چشم بسیار مفید است.
</div>

<div class="success-box">
<strong>✅ مدیریت چند نمودار هم‌زمان (Multi-Tab):</strong> امکان باز کردن چندین نمودار در تب‌های مجزا و سوئیچ سریع بین آن‌ها. این ویژگی برای پروژه‌های بزرگ با نمودارهای متعدد، بسیار کارآمد است.
</div>

<div class="success-box">
<strong>✅ خروجی PNG با کیفیت:</strong> امکان Export نمودارها به فرمت PNG با قابلیت انتخاب رنگ پس‌زمینه یا شفافیت. مناسب برای استفاده در مستندات، ارائه‌ها و گزارش‌ها.
</div>

<div class="success-box">
<strong>✅ رابط کاربری شهودی و کاربرپسند:</strong> طراحی رابط کاربری بر اساس اصول UX مدرن، با ابزارهای دسته‌بندی‌شده، کلیدهای میانبر، Properties Panel و ویرایشگرهای پیشرفته. منحنی یادگیری بسیار کوتاه است.
</div>

<div class="highlight-box">
<strong>💎 مزیت رقابتی کلیدی:</strong> این ابزار تنها راه‌حلی است که تمامی نیازمندی‌های یک ابزار حرفه‌ای UML را در یک بسته سبک، رایگان، آفلاین و کاملاً فارسی ارائه می‌دهد. هیچ ابزار دیگری در بازار چنین ترکیبی از ویژگی‌ها را فراهم نمی‌آورد.
</div>
</div>
</div>

<div class="section">
<div class="section-header">
<div class="section-number">۳</div>
<div class="section-title">
<h3>ارزش‌های خلق‌شده در سازمان</h3>
<div class="subtitle">Organizational Value Creation</div>
</div>
</div>
<div class="section-content">
<p>استفاده از این ابزار در سطح سازمان، ارزش‌های استراتژیک و عملیاتی متعددی ایجاد می‌کند که در سه دسته اصلی قابل دسته‌بندی هستند:</p>

<div class="value-grid">
<div class="value-card">
<div class="icon">💰</div>
<h4>صرفه‌جویی مالی</h4>
<p>حذف هزینه‌های لایسنس، کاهش هزینه‌های آموزشی، کاهش زمان مدل‌سازی و افزایش بهره‌وری تیم</p>
</div>
<div class="value-card">
<div class="icon">⚡</div>
<h4>افزایش چابکی</h4>
<p>سرعت بخشیدن به فرآیند طراحی، امکان مدل‌سازی سریع در جلسات، و کاهش زمان تبدیل ایده به مستند</p>
</div>
<div class="value-card">
<div class="icon">🎯</div>
<h4>بهبود کیفیت</h4>
<p>استانداردسازی نمودارها، کاهش خطاهای طراحی، و افزایش دقت در انتقال مفاهیم بین تیم‌ها</p>
</div>
<div class="value-card">
<div class="icon">🔒</div>
<h4>امنیت داده‌ها</h4>
<p>حفظ کامل حریم داده‌های معماری، عدم وابستگی به سرورهای خارجی، و کنترل کامل بر اطلاعات حساس</p>
</div>
<div class="value-card">
<div class="icon">🤝</div>
<h4>بهبود ارتباطات</h4>
<p>زبان بصری مشترک بین تیم‌های فنی و کسب‌وکار، کاهش سوءتفاهم‌ها، و افزایش شفافیت در مستندات</p>
</div>
<div class="value-card">
<div class="icon">📚</div>
<h4>دانش سازمانی</h4>
<p>ایجاد مخزن استاندارد از نمودارها، تسهیل Onboarding نیروهای جدید، و حفظ دانش معماری</p>
</div>
<div class="value-card">
<div class="icon">🌐</div>
<h4>استقلال فناورانه</h4>
<p>عدم وابستگی به ابزارهای خارجی، قابلیت اجرا در هر محیطی، و سازگاری با محدودیت‌های شبکه</p>
</div>
<div class="value-card">
<div class="icon">🚀</div>
<h4>توسعه‌پذیری</h4>
<p>معماری ماژولار امکان افزودن نمودارهای جدید، سفارشی‌سازی و یکپارچه‌سازی با ابزارهای دیگر را فراهم می‌آورد</p>
</div>
<div class="value-card">
<div class="icon">📊</div>
<h4>حرفه‌ای‌گری</h4>
<p>ارتقای سطح بلوغ مهندسی سازمان، استانداردسازی فرآیندهای طراحی، و افزایش اعتبار مستندات فنی</p>
</div>
</div>

<table class="styled-table">
<thead>
<tr>
<th>دسته ارزش</th>
<th>شاخص کلیدی</th>
<th>تأثیر مورد انتظار</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>مالی</strong></td>
<td>کاهش هزینه‌های ابزار</td>
<td style="color: var(--success); font-weight: 600;">حذف ۱۰۰٪ هزینه لایسنس</td>
</tr>
<tr>
<td><strong>عملیاتی</strong></td>
<td>زمان مدل‌سازی</td>
<td style="color: var(--success); font-weight: 600;">کاهش ۴۰-۶۰٪ زمان</td>
</tr>
<tr>
<td><strong>کیفی</strong></td>
<td>دقت مستندات</td>
<td style="color: var(--success); font-weight: 600;">افزایش چشمگیر</td>
</tr>
<tr>
<td><strong>امنیتی</strong></td>
<td>حفظ حریم داده‌ها</td>
<td style="color: var(--success); font-weight: 600;">تضمین کامل</td>
</tr>
<tr>
<td><strong>آموزشی</strong></td>
<td>زمان یادگیری</td>
<td style="color: var(--success); font-weight: 600;">کاهش ۷۰٪ منحنی یادگیری</td>
</tr>
<tr class="highlight-row">
<td><strong>استراتژیک</strong></td>
<td>بلوغ مهندسی سازمان</td>
<td style="color: var(--success); font-weight: 700; font-size: 15px;">ارتقای سطح بلوغ</td>
</tr>
</tbody>
</table>

<div class="info-box">
<strong>💡 نکته کلیدی:</strong> ارزش اصلی این ابزار در ترکیب منحصر‌به‌فرد «رایگان + آفلاین + فارسی + جامع» است. این ترکیب در هیچ ابزار دیگری در بازار یافت نمی‌شود و برای سازمان‌های ایرانی یک مزیت رقابتی پایدار ایجاد می‌کند.
</div>
</div>
</div>

<div class="section">
<div class="section-header">
<div class="section-number">۴</div>
<div class="section-title">
<h3>معماری دقیق فنی پروژه</h3>
<div class="subtitle">Technical Architecture Deep Dive</div>
</div>
</div>
<div class="section-content">
<p>معماری این پروژه بر پایه اصول مهندسی نرم‌افزار مدرن، الگوهای طراحی اثبات‌شده و رویکرد State-Centric طراحی شده است. در ادامه، لایه‌های مختلف معماری و اجزای کلیدی آن تشریح می‌گردد.</p>

<div class="arch-layer">
<h4>🎨 لایه ۱: رابط کاربری (UI Layer)</h4>
<p>این لایه مسئول ارائه تجربه کاربری حرفه‌ای و تعامل با کاربر است. شامل Sidebar ابزارها، Tab Bar برای مدیریت چند نمودار، Properties Panel برای ویرایش ویژگی‌ها، و Modal‌های مختلف (ایجاد نمودار، تنظیمات، ذخیره، Export PNG و ویرایشگر Class) می‌باشد.</p>
<ul>
<li><strong>تکنولوژی:</strong> HTML5 Semantic + CSS3 (با CSS Variables برای تم داینامیک) + Tailwind CSS (CDN)</li>
<li><strong>ویژگی کلیدی:</strong> پشتیبانی کامل RTL، فونت Vazirmatn، تم تاریک/روشن، طراحی Responsive</li>
</ul>
</div>

<div class="arch-layer">
<h4>🧠 لایه ۲: مدیریت وضعیت (State Management Layer)</h4>
<p>قلب تپنده برنامه که تمامی وضعیت‌های سراسری را در یک آبجکت مرکزی نگهداری می‌کند. این رویکرد باعث سادگی، قابلیت پیش‌بینی و نگهداری آسان کد می‌شود.</p>
<div class="code-block">
<span class="keyword">const</span> state = {<br>
&nbsp;&nbsp;diagrams: [],&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// لیست همه نمودارها</span><br>
&nbsp;&nbsp;activeDiagramId: <span class="keyword">null</span>,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// شناسه نمودار فعال</span><br>
&nbsp;&nbsp;currentTool: <span class="string">'select'</span>,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// ابزار انتخاب‌شده</span><br>
&nbsp;&nbsp;selectedElement: <span class="keyword">null</span>,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// المان انتخاب‌شده</span><br>
&nbsp;&nbsp;isDragging: <span class="keyword">false</span>,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// وضعیت Drag</span><br>
&nbsp;&nbsp;isDrawingRelationship: <span class="keyword">false</span>,&nbsp;&nbsp;<span class="comment">// وضعیت رسم رابطه</span><br>
&nbsp;&nbsp;mousePos: { x:0, y:0 },&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// موقعیت ماوس</span><br>
&nbsp;&nbsp;sidebarVisible: <span class="keyword">true</span>,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// وضعیت Sidebar</span><br>
&nbsp;&nbsp;gridType: <span class="string">'lines'</span>,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// نوع خط‌کشی بوم</span><br>
&nbsp;&nbsp;darkMode: <span class="keyword">true</span>,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// تم تاریک/روشن</span><br>
&nbsp;&nbsp;fileMetadata: { ... }&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// متادیتای پروژه</span><br>
&nbsp;&nbsp;<span class="ellipsis">// ... سایر ویژگی‌های وضعیت</span><br>
};
</div>
</div>

<div class="arch-layer">
<h4>🎨 لایه ۳: موتور رسم (Rendering Engine)</h4>
<p>موتور رسم بر پایه HTML5 Canvas 2D API و رویکرد Immediate Mode Rendering طراحی شده است. در این رویکرد، در هر تغییر وضعیت، کل بوم پاک‌سازی شده و تمامی المان‌ها از نو رسم می‌شوند.</p>
<ul>
<li><strong>تابع اصلی رسم:</strong> <code>function draw() { ... }</code> - چرخه اصلی رسم که Grid، المان‌ها و روابط را مدیریت می‌کند</li>
<li><strong>تابع Dispatch:</strong> <code>function drawDiag(c,d,tc) { ... }</code> - بر اساس نوع نمودار، تابع اختصاصی را فراخوانی می‌کند</li>
<li><strong>توابع اختصاصی هر نمودار:</strong>
<ul style="margin-top: 8px;">
<li><code>function drawUseCase(c,d,tc) { ... }</code></li>
<li><code>function drawStateMachine(c,d,tc) { ... }</code></li>
<li><code>function drawActivity(c,d,tc) { ... }</code></li>
<li><code>function drawComponent(c,d,tc) { ... }</code></li>
<li><code>function drawDeployment(c,d,tc) { ... }</code></li>
<li><code>function drawClassDiagram(c,d,tc) { ... }</code></li>
<li><code>function drawSequenceDiagram(c,d,tc) { ... }</code></li>
<li><code>function drawSwimlaneDiagram(c,d,tc) { ... }</code></li>
</ul>
</li>
<li><strong>تابع رسم Grid:</strong> <code>function drawGrid(c,w,h,tc,trans) { ... }</code> - پشتیبانی از سه حالت Lines, Dots, None</li>
</ul>
</div>

<div class="arch-layer">
<h4>🎯 لایه ۴: سیستم تعامل و Hit Testing</h4>
<p>این لایه مسئول تشخیص موقعیت ماوس، انتخاب المان‌ها، Drag & Drop و رسم روابط است.</p>
<ul>
<li><strong>تابع Hit Test:</strong> <code>function hitTest(x,y) { ... }</code> - بر اساس نوع نمودار، الگوریتم‌های متفاوتی برای تشخیص المان زیر ماوس اجرا می‌کند</li>
<li><strong>مدیریت رویدادها:</strong>
<ul style="margin-top: 8px;">
<li><code>canvas.addEventListener('mousedown', e => { ... })</code> - شروع Drag یا رسم رابطه</li>
<li><code>canvas.addEventListener('mousemove', e => { ... })</code> - به‌روزرسانی موقعیت المان‌ها</li>
<li><code>canvas.addEventListener('mouseup', e => { ... })</code> - پایان Drag یا تکمیل رابطه</li>
<li><code>canvas.addEventListener('dblclick', e => { ... })</code> - باز کردن ویرایشگرها</li>
</ul>
</li>
</ul>
</div>

<div class="arch-layer">
<h4>🗂️ لایه ۵: رجیستری نمودارها (Diagram Registry)</h4>
<p>الگوی طراحی Registry Pattern برای تعریف مرکزی انواع نمودارها و قابلیت‌های آن‌ها به‌کار رفته است.</p>
<div class="code-block">
<span class="keyword">const</span> DIAGRAM_TYPES = {<br>
&nbsp;&nbsp;usecase: {<br>
&nbsp;&nbsp;&nbsp;&nbsp;id: <span class="string">'usecase'</span>,<br>
&nbsp;&nbsp;&nbsp;&nbsp;name: <span class="string">'نمودار مورد کاربرد (Use Case)'</span>,<br>
&nbsp;&nbsp;&nbsp;&nbsp;icon: <span class="string">'⭕'</span>,<br>
&nbsp;&nbsp;&nbsp;&nbsp;available: <span class="keyword">true</span>,<br>
&nbsp;&nbsp;&nbsp;&nbsp;tools: [<span class="string">'select'</span>, <span class="string">'actor'</span>, <span class="string">'useCase'</span>, ...],<br>
&nbsp;&nbsp;&nbsp;&nbsp;groups: [<span class="string">'selection'</span>, <span class="string">'uc-elements'</span>, ...]<br>
&nbsp;&nbsp;},<br>
&nbsp;&nbsp;statemachine: { <span class="ellipsis">/* ... */</span> },<br>
&nbsp;&nbsp;activity: { <span class="ellipsis">/* ... */</span> },<br>
&nbsp;&nbsp;component: { <span class="ellipsis">/* ... */</span> },<br>
&nbsp;&nbsp;deployment: { <span class="ellipsis">/* ... */</span> },<br>
&nbsp;&nbsp;class: { <span class="ellipsis">/* ... */</span> },<br>
&nbsp;&nbsp;sequence: { <span class="ellipsis">/* ... */</span> },<br>
&nbsp;&nbsp;swimlane: { <span class="ellipsis">/* ... */</span> }<br>
};
</div>
<p>این رجیستری به‌عنوان Single Source of Truth برای ابزارها، گروه‌ها و قابلیت‌های هر نمودار عمل می‌کند و افزودن نمودار جدید را بسیار آسان می‌سازد.</p>
</div>

<div class="arch-layer">
<h4>💾 لایه ۶: سیستم Persistence</h4>
<p>سیستم ذخیره‌سازی و بازیابی مبتنی بر JSON File API است. این رویکرد مزایای متعددی نسبت به LocalStorage یا Backend دارد.</p>
<ul>
<li><strong>تابع ساخت داده ذخیره:</strong> <code>function buildSaveData() { ... }</code> - ساختار کامل JSON شامل متادیتا، ترجیحات و همه نمودارها</li>
<li><strong>تابع ذخیره:</strong> <code>document.getElementById('confirmSaveBtn').addEventListener('click', () => { ... })</code> - ایجاد Blob و دانلود فایل</li>
<li><strong>تابع بارگذاری:</strong> <code>document.getElementById('fileInput').addEventListener('change', e => { ... })</code> - خواندن فایل JSON و بازیابی وضعیت</li>
</ul>
</div>

<div class="arch-layer">
<h4>🎨 لایه ۷: سیستم تم و رنگ</h4>
<p>سیستم تم بر پایه CSS Custom Properties (Variables) طراحی شده است. با تغییر کلاس <code>body.dark</code>، تمامی متغیرهای رنگی به‌صورت آنی تغییر می‌کنند.</p>
<ul>
<li><strong>تابع دریافت رنگ‌ها:</strong> <code>function getTC() { ... }</code> - استخراج رنگ‌های فعال از CSS Variables در زمان اجرا</li>
<li><strong>تابع اعمال تم:</strong> <code>function applyTheme() { ... }</code> - تغییر کلاس body و به‌روزرسانی UI</li>
</ul>
</div>

<div class="arch-layer">
<h4>🔧 لایه ۸: مدیریت نمودارها</h4>
<p>توابع مدیریت چرخه حیات نمودارها (ایجاد، سوئیچ، حذف) در این لایه قرار دارند.</p>
<ul>
<li><code>function createNewDiagram(type='usecase') { ... }</code></li>
<li><code>function switchDiagram(id) { ... }</code></li>
<li><code>function deleteDiagram(id) { ... }</code></li>
<li><code>function renderTabs() { ... }</code> - رسم Tab Bar</li>
<li><code>function updateToolVisibility() { ... }</code> - نمایش/مخفی کردن ابزارها بر اساس نوع نمودار فعال</li>
</ul>
</div>

<div class="arch-layer">
<h4>🗂️ لایه ۹: ویرایشگرهای تخصصی</h4>
<p>برای المان‌های پیچیده مانند Class، ویرایشگرهای اختصاصی طراحی شده‌اند.</p>
<ul>
<li><code>function openClassEditor(classId) { ... }</code> - باز کردن ویرایشگر Class با Attributes و Methods</li>
<li><code>function renderAttributesTable() { ... }</code> - رسم جدول ویژگی‌ها</li>
<li><code>function renderMethodsTable() { ... }</code> - رسم جدول متدها</li>
</ul>
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">الگوهای طراحی به‌کار رفته:</h4>
<table class="styled-table">
<thead>
<tr>
<th>الگو</th>
<th>محل کاربرد</th>
<th>مزیت</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Registry Pattern</strong></td>
<td><code>DIAGRAM_TYPES</code></td>
<td>مرجع مرکزی، توسعه‌پذیری آسان</td>
</tr>
<tr>
<td><strong>State Pattern</strong></td>
<td>آبجکت <code>state</code></td>
<td>مدیریت متمرکز وضعیت</td>
</tr>
<tr>
<td><strong>Strategy Pattern</strong></td>
<td>توابع <code>draw*</code></td>
<td>انتخاب استراتژی رسم بر اساس نوع</td>
</tr>
<tr>
<td><strong>Command Pattern</strong></td>
<td>مدیریت ابزارها</td>
<td>رفتارهای مشخص برای هر ابزار</td>
</tr>
<tr>
<td><strong>Observer Pattern</strong></td>
<td>Event Listeners</td>
<td>واکنش به رویدادهای کاربر</td>
</tr>
<tr>
<td><strong>Factory Pattern</strong></td>
<td><code>createNewDiagram()</code></td>
<td>ساخت ساختار اولیه نمودار</td>
</tr>
<tr>
<td><strong>Singleton Pattern</strong></td>
<td>Canvas Context، State</td>
<td>تنها یک نمونه از منابع حیاتی</td>
</tr>
</tbody>
</table>

<div class="info-box">
<strong>💡 نکته معماری:</strong> انتخاب Vanilla JS به جای فریم‌ورک‌هایی مانند React یا Angular، باعث کاهش چشمگیر حجم نهایی، اجرای سریع‌تر، عدم وابستگی به ابزارهای Build و سادگی نگهداری شده است. این رویکرد برای ابزارهای تخصصی و آفلاین، انتخاب بهینه‌ای است.
</div>
</div>
</div>

<div class="section">
<div class="section-header">
<div class="section-number">۵</div>
<div class="section-title">
<h3>مخاطبان هدف: سازمان‌ها و تیم‌ها</h3>
<div class="subtitle">Target Audience: Organizations & Teams</div>
</div>
</div>
<div class="section-content">
<p>این ابزار برای طیف گسترده‌ای از سازمان‌ها و تیم‌های نرم‌افزاری طراحی شده است. در ادامه، مخاطبان هدف و موارد استفاده مناسب برای هر دسته تشریح می‌گردد.</p>

<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">الف) سازمان‌های مناسب:</h4>

<div class="audience-grid">
<div class="audience-card">
<div class="icon">🏢</div>
<h4>شرکت‌های نرم‌افزاری</h4>
<p>تیم‌های توسعه که نیاز به مدل‌سازی معماری، طراحی کلاس‌ها و مستندسازی سیستم‌ها دارند. مناسب برای شرکت‌های محصول‌محور و پروژه‌ای.</p>
</div>
<div class="audience-card">
<div class="icon">🚀</div>
<h4>استارتاپ‌ها</h4>
<p>تیم‌های کوچک که به ابزار سبک، رایگان و سریع نیاز دارند. مناسب برای MVP، طراحی اولیه و ارائه به سرمایه‌گذاران.</p>
</div>
<div class="audience-card">
<div class="icon">🏛️</div>
<h4>سازمان‌های دولتی و نظامی</h4>
<p>سازمان‌هایی که به دلیل محدودیت‌های امنیتی، نمی‌توانند از ابزارهای ابری استفاده کنند و نیاز به راه‌حل کاملاً آفلاین دارند.</p>
</div>
<div class="audience-card">
<div class="icon">🏦</div>
<h4>بانک‌ها و مؤسسات مالی</h4>
<p>سازمان‌هایی با الزامات امنیتی بالا که نیاز به حفظ کامل حریم داده‌های معماری حساس خود دارند.</p>
</div>
<div class="audience-card">
<div class="icon">🎓</div>
<h4>دانشگاه‌ها و مراکز آموزشی</h4>
<p>اساتید و دانشجویان رشته‌های مهندسی نرم‌افزار، علوم کامپیوتر و IT برای تدریس و یادگیری UML.</p>
</div>
<div class="audience-card">
<div class="icon">🏥</div>
<h4>سازمان‌های بهداشتی و درمانی</h4>
<p>تیم‌های فنی که نیاز به مدل‌سازی فرآیندهای پیچیده کسب‌وکار و سیستم‌های اطلاعاتی دارند.</p>
</div>
<div class="audience-card">
<div class="icon">🏭</div>
<h4>شرکت‌های صنعتی و تولیدی</h4>
<p>تیم‌های IT که نیاز به مدل‌سازی سیستم‌های ERP، CRM و فرآیندهای تولید دارند.</p>
</div>
<div class="audience-card">
<div class="icon">🌐</div>
<h4>شرکت‌های مشاوره و پیمانکاری</h4>
<p>مشاورانی که نیاز به ارائه مستندات حرفه‌ای به کارفرمایان مختلف دارند و می‌خواهند ابزار یکپارچه‌ای داشته باشند.</p>
</div>
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">ب) تیم‌های مناسب:</h4>

<div class="audience-grid">
<div class="audience-card">
<div class="icon">👨‍💼</div>
<h4>معماران نرم‌افزار</h4>
<p>برای طراحی معماری کلان، مدل‌سازی کامپوننت‌ها، استقرار و ارتباط بین لایه‌های مختلف سیستم.</p>
</div>
<div class="audience-card">
<div class="icon">📊</div>
<h4>تحلیل‌گران کسب‌وکار (BA)</h4>
<p>برای مدل‌سازی نیازمندی‌ها، فرآیندهای کسب‌وکار، Use Caseها و Swimlaneها.</p>
</div>
<div class="audience-card">
<div class="icon">💻</div>
<h4>توسعه‌دهندگان Backend</h4>
<p>برای طراحی Class Diagram، Sequence Diagram و State Machine برای پیاده‌سازی دقیق.</p>
</div>
<div class="audience-card">
<div class="icon">🎨</div>
<h4>توسعه‌دهندگان Frontend</h4>
<p>برای مدل‌سازی Componentها، تعاملات UI و ارتباط بین کامپوننت‌ها.</p>
</div>
<div class="audience-card">
<div class="icon">🧪</div>
<h4>تیم‌های QA و تست</h4>
<p>برای مدل‌سازی سناریوهای تست، فرآیندهای کسب‌وکار و درک بهتر سیستم.</p>
</div>
<div class="audience-card">
<div class="icon">📋</div>
<h4>مدیران پروژه (PM)</h4>
<p>برای مستندسازی معماری، ارتباط با ذینفعان و ارائه گزارش‌های بصری.</p>
</div>
<div class="audience-card">
<div class="icon">🎯</div>
<h4>Product Owners</h4>
<p>برای مدل‌سازی نیازمندی‌ها، Use Caseها و ارتباط با تیم فنی.</p>
</div>
<div class="audience-card">
<div class="icon">🔧</div>
<h4>تیم‌های DevOps</h4>
<p>برای مدل‌سازی Deployment Diagram، معماری زیرساخت و ارتباط بین سرویس‌ها.</p>
</div>
</div>

<h4 style="margin-top: 25px; color: var(--primary); font-size: 16px;">ج) موارد استفاده مناسب:</h4>

<ul class="module-list">
<li><strong>📐 طراحی معماری سیستم:</strong> مدل‌سازی معماری کلان، میکروسرویس‌ها، و ارتباط بین کامپوننت‌ها</li>
<li><strong>📋 مستندسازی نیازمندی‌ها:</strong> رسم Use Case Diagram برای انتقال نیازمندی‌ها به تیم فنی</li>
<li><strong>🔄 مدل‌سازی فرآیندها:</strong> رسم Activity و Swimlane Diagram برای فرآیندهای کسب‌وکار</li>
<li><strong>🗂️ طراحی شیءگرا:</strong> رسم Class Diagram برای طراحی کلاس‌ها و روابط آن‌ها</li>
<li><strong>⏱️ مدل‌سازی تعاملات:</strong> رسم Sequence Diagram برای سناریوهای اجرایی</li>
<li><strong>🔁 مدل‌سازی چرخه حیات:</strong> رسم State Machine Diagram برای موجودیت‌های با وضعیت‌های گسسته</li>
<li><strong>🖥️ مستندسازی زیرساخت:</strong> رسم Deployment Diagram برای معماری فیزیکی</li>
<li><strong>🎓 آموزش و یادگیری:</strong> تدریس مفاهیم UML و مدل‌سازی نرم‌افزار</li>
<li><strong>📊 ارائه به ذینفعان:</strong> ایجاد نمودارهای حرفه‌ای برای جلسات و گزارش‌ها</li>
<li><strong>🔍 تحلیل و دیباگ:</strong> مدل‌سازی سناریوها برای درک بهتر رفتار سیستم</li>
</ul>

<div class="highlight-box">
<strong>💎 نتیجه‌گیری:</strong> این ابزار برای هر سازمان یا تیمی که نیاز به مدل‌سازی بصری سیستم‌های نرم‌افزاری دارد، مناسب است. به‌ویژه برای سازمان‌های ایرانی که به پشتیبانی کامل فارسی، محیط آفلاین و امنیت داده‌ها نیاز دارند، این ابزار یک انتخاب ایده‌آل است.
</div>
</div>
</div>

<div class="section">
<div class="section-header">
<div class="section-number">۶</div>
<div class="section-title">
<h3>جمع‌بندی نهایی و پیشنهاد</h3>
<div class="subtitle">Final Summary & Recommendation</div>
</div>
</div>
<div class="section-content">
<p>«ابزار رسم نمودارهای UML» پاسخی جامع و حرفه‌ای به چالش‌های موجود در حوزه مدل‌سازی نرم‌افزار است. این ابزار با ترکیب منحصر‌به‌فرد «رایگان + آفلاین + فارسی + جامع»، ارزش‌های استراتژیک و عملیاتی متعددی برای سازمان‌ها ایجاد می‌کند.</p>

<table class="styled-table">
<thead>
<tr>
<th>شاخص</th>
<th>ابزارهای تجاری</th>
<th>ابزارهای ابری</th>
<th>این ابزار</th>
</tr>
</thead>
<tbody>
<tr>
<td>هزینه</td>
<td style="color: var(--danger); font-weight: 600;">بسیار بالا</td>
<td style="color: var(--warning); font-weight: 600;">اشتراک ماهیانه</td>
<td style="color: var(--success); font-weight: 700;">کاملاً رایگان</td>
</tr>
<tr>
<td>نیاز به اینترنت</td>
<td>اختیاری</td>
<td style="color: var(--danger); font-weight: 600;">الزامی</td>
<td style="color: var(--success); font-weight: 700;">کاملاً آفلاین</td>
</tr>
<tr>
<td>امنیت داده‌ها</td>
<td>متوسط</td>
<td style="color: var(--danger); font-weight: 600;">ریسک بالا</td>
<td style="color: var(--success); font-weight: 700;">تضمین کامل</td>
</tr>
<tr>
<td>پشتیبانی فارسی</td>
<td style="color: var(--warning); font-weight: 600;">محدود</td>
<td style="color: var(--danger); font-weight: 600;">ضعیف</td>
<td style="color: var(--success); font-weight: 700;">کامل و حرفه‌ای</td>
</tr>
<tr>
<td>تعداد نمودارها</td>
<td>۸+ نمودار</td>
<td>۴-۶ نمودار</td>
<td style="color: var(--success); font-weight: 700;">۸ نمودار کامل</td>
</tr>
<tr>
<td>حجم و نصب</td>
<td style="color: var(--danger); font-weight: 600;">چند GB + نصب</td>
<td>بدون نصب</td>
<td style="color: var(--success); font-weight: 700;">&lt;1 MB بدون نصب</td>
</tr>
<tr class="highlight-row">
<td><strong>ارزش کلی</strong></td>
<td>—</td>
<td>—</td>
<td style="color: var(--success); font-weight: 700; font-size: 15px;">بسیار بالاتر</td>
</tr>
</tbody>
</table>

<div class="conclusion-box">
<h3>🎯 پیشنهاد نهایی</h3>
<p>با توجه به تحلیل جامع نیازمندی‌ها، مزایا، ارزش‌های سازمانی و معماری فنی، استفاده از این ابزار برای تمامی سازمان‌ها و تیم‌های نرم‌افزاری که به مدل‌سازی UML نیاز دارند، قویاً توصیه می‌شود.</p>
<p>این ابزار نه تنها هزینه‌ها را به شکل قابل‌توجهی کاهش می‌دهد، بلکه کیفیت مستندات، سرعت طراحی و امنیت داده‌ها را به‌طور هم‌زمان ارتقا می‌بخشد.</p>
<div class="highlight">
<strong>💎 ارزش پیشنهادی:</strong><br>
ابزاری کاملاً رایگان، آفلاین، فارسی و جامع که تمامی نیازمندی‌های مدل‌سازی UML را در یک بسته سبک و حرفه‌ای ارائه می‌دهد.
</div>
</div>

<div class="info-box">
<strong>📞 گام بعدی:</strong> برای شروع استفاده، کافی است فایل HTML ابزار را دانلود کرده و در مرورگر باز کنید. هیچ نصب یا پیکربندی نیاز نیست. مستندات آموزشی کامل نیز برای یادگیری سریع در دسترس است.
</div>
</div>
</div>

<div class="page-info">
این مستند جهت معرفی و تحلیل ابزار رسم نمودارهای UML تهیه شده است. استفاده از این ابزار برای تمامی سازمان‌ها و تیم‌های نرم‌افزاری آزاد و رایگان است.
</div>
</div>
<footer class="document-footer">
<div class="footer-brand">طراحی، تحلیل و توسعه: هادی خزاعی اصل</div>
<div class="footer-divider"></div>
<div>تاریخ تهیه: ۱۷ شهریور ۱۴۰۵ — ساعت ۱۰:۳۰ &nbsp;|&nbsp; نسخه: ۱.۰ &nbsp;|&nbsp; طبقه‌بندی: عمومی / معرفی محصول</div>
</footer>
</div>
</body>
</html>

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Resources\Tools\Document\xUMLDrawer_Supported_UML_Diagrams_Tutorial.html

<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>مستند آموزشی جامع نمودارهای UML | راهنمای کامل با مثال‌های عملی</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap');
:root {
--primary: #1a365d;
--primary-light: #2c5282;
--accent: #c9a961;
--accent-dark: #a8893f;
--bg: #faf9f6;
--card-bg: #ffffff;
--text: #1a202c;
--text-muted: #4a5568;
--border: #e2e8f0;
--success: #38a169;
--danger: #e53e3e;
--warning: #dd6b20;
--info: #3182ce;
--shadow: 0 4px 6px rgba(0,0,0,0.05), 0 10px 30px rgba(26,54,93,0.08);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Vazirmatn', Tahoma, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.9;
font-size: 15px;
padding: 30px 15px;
}
.document-container {
max-width: 950px;
margin: 0 auto;
background: var(--card-bg);
box-shadow: var(--shadow);
border-radius: 12px;
overflow: hidden;
}
.document-header {
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);
color: white;
padding: 50px 40px 40px;
position: relative;
border-bottom: 4px solid var(--accent);
}
.document-header::before {
content: '';
position: absolute;
top: 0; right: 0;
width: 200px; height: 200px;
background: radial-gradient(circle, rgba(201,169,97,0.15) 0%, transparent 70%);
border-radius: 50%;
transform: translate(50%, -50%);
}
.header-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
position: relative;
z-index: 1;
}
.company-badge {
display: flex;
align-items: center;
gap: 12px;
}
.company-logo {
width: 60px; height: 60px;
background: var(--accent);
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
.doc-meta {
text-align: left;
font-size: 12px;
opacity: 0.9;
}
.doc-meta div { margin-bottom: 3px; }
.document-title {
text-align: center;
position: relative;
z-index: 1;
padding: 20px 0;
}
.document-title .label {
display: inline-block;
background: rgba(201,169,97,0.2);
color: var(--accent);
padding: 4px 16px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
margin-bottom: 15px;
border: 1px solid rgba(201,169,97,0.4);
}
.document-title h1 {
font-size: 28px;
font-weight: 800;
margin-bottom: 10px;
letter-spacing: -0.5px;
}
.document-title h2 {
font-size: 16px;
font-weight: 400;
opacity: 0.9;
}
.document-body {
padding: 40px;
}
.intro-text {
background: linear-gradient(to left, #f7fafc, #edf2f7);
border-right: 4px solid var(--accent);
padding: 20px 25px;
border-radius: 8px;
margin-bottom: 35px;
font-size: 14px;
color: var(--text-muted);
}
.section {
margin-bottom: 40px;
page-break-inside: avoid;
}
.section-header {
display: flex;
align-items: center;
gap: 15px;
padding-bottom: 12px;
border-bottom: 2px solid var(--border);
margin-bottom: 20px;
}
.section-number {
width: 50px; height: 50px;
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 800;
font-size: 20px;
flex-shrink: 0;
box-shadow: 0 4px 10px rgba(26,54,93,0.2);
}
.section-title { flex: 1; }
.section-title h3 {
font-size: 19px;
font-weight: 700;
color: var(--primary);
margin-bottom: 2px;
}
.section-title .subtitle {
font-size: 12px;
color: var(--text-muted);
}
.section-content {
padding-right: 10px;
}
.section-content p {
margin-bottom: 12px;
text-align: justify;
text-indent: 20px;
}
.section-content p.no-indent { text-indent: 0; }
.highlight-box {
background: linear-gradient(to left, #fffaf0, #fef5e7);
border: 1px solid #f6e05e;
border-right: 4px solid var(--accent);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.highlight-box strong {
color: var(--accent-dark);
}
.info-box {
background: linear-gradient(to left, #ebf8ff, #e6f6ff);
border: 1px solid #90cdf4;
border-right: 4px solid var(--primary-light);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.success-box {
background: linear-gradient(to left, #f0fff4, #e6fffa);
border: 1px solid #9ae6b4;
border-right: 4px solid var(--success);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.warning-box {
background: linear-gradient(to left, #fffaf0, #fef5e7);
border: 1px solid #fbd38d;
border-right: 4px solid var(--warning);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.styled-table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
page-break-inside: auto;
}
.styled-table thead {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
}
.styled-table th {
padding: 14px 16px;
text-align: right;
font-weight: 600;
font-size: 13px;
}
.styled-table td {
padding: 13px 16px;
border-bottom: 1px solid var(--border);
font-size: 13px;
}
.styled-table tbody tr:last-child td { border-bottom: none; }
.styled-table tbody tr:nth-child(even) { background: #f8fafc; }
.styled-table .highlight-row {
background: #fffaf0 !important;
font-weight: 600;
}
.diagram-elements {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
margin: 25px 0;
}
.element-card {
background: #f8fafc;
border: 2px solid var(--border);
border-radius: 10px;
padding: 18px;
transition: all 0.2s;
page-break-inside: avoid;
}
.element-card:hover {
border-color: var(--primary-light);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(26,54,93,0.1);
}
.element-card .icon {
font-size: 28px;
margin-bottom: 8px;
}
.element-card h4 {
font-size: 15px;
font-weight: 700;
color: var(--primary);
margin-bottom: 6px;
}
.element-card p {
font-size: 12px;
color: var(--text-muted);
text-indent: 0 !important;
margin-bottom: 0 !important;
}
.example-box {
background: linear-gradient(to left, #f0fff4, #e6fffa);
border: 1px solid #9ae6b4;
border-right: 4px solid var(--success);
padding: 22px;
border-radius: 8px;
margin: 25px 0;
}
.example-box h4 {
color: var(--success);
font-size: 15px;
font-weight: 700;
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 8px;
}
.example-box ul {
padding-right: 20px;
margin-top: 10px;
}
.example-box li {
margin-bottom: 8px;
font-size: 13px;
}
.relationships-list {
list-style: none;
padding: 0;
margin: 15px 0;
}
.relationships-list li {
background: #f8fafc;
border-right: 3px solid var(--primary-light);
padding: 12px 18px;
margin-bottom: 10px;
border-radius: 6px;
font-size: 13px;
display: flex;
align-items: flex-start;
gap: 10px;
}
.relationships-list li strong {
color: var(--primary);
white-space: nowrap;
}
.conclusion-box {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
padding: 30px;
border-radius: 12px;
margin: 30px 0;
text-align: center;
box-shadow: 0 8px 20px rgba(26,54,93,0.3);
}
.conclusion-box h3 {
font-size: 22px;
font-weight: 800;
margin-bottom: 15px;
color: var(--accent);
}
.conclusion-box p {
font-size: 14px;
line-height: 1.8;
text-indent: 0 !important;
margin-bottom: 15px;
}
.conclusion-box .highlight {
background: rgba(201,169,97,0.2);
padding: 15px 20px;
border-radius: 8px;
margin-top: 20px;
border: 1px solid rgba(201,169,97,0.4);
}
.document-footer {
background: var(--primary);
color: white;
padding: 25px 40px;
text-align: center;
font-size: 12px;
margin-top: 40px;
}
.document-footer .footer-brand {
font-weight: 700;
color: var(--accent);
margin-bottom: 5px;
font-size: 14px;
}
.footer-divider {
width: 60px;
height: 2px;
background: var(--accent);
margin: 10px auto;
}
.page-info {
text-align: center;
padding: 15px;
font-size: 11px;
color: var(--text-muted);
border-top: 1px solid var(--border);
margin-top: 30px;
}
.toc {
background: linear-gradient(to left, #f7fafc, #edf2f7);
border: 1px solid var(--border);
border-radius: 8px;
padding: 20px;
margin: 25px 0;
}
.toc h4 {
color: var(--primary);
font-size: 16px;
font-weight: 700;
margin-bottom: 15px;
border-bottom: 2px solid var(--border);
padding-bottom: 8px;
}
.toc ul {
list-style: none;
padding: 0;
}
.toc li {
padding: 8px 0;
border-bottom: 1px dashed var(--border);
}
.toc li:last-child { border-bottom: none; }
.toc a {
color: var(--text);
text-decoration: none;
display: flex;
align-items: center;
gap: 8px;
transition: color 0.2s;
}
.toc a:hover { color: var(--primary-light); }
.toc .num {
background: var(--primary);
color: white;
width: 24px;
height: 24px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: 700;
flex-shrink: 0;
}
@media print {
body { background: white; padding: 0; }
.document-container { box-shadow: none; border-radius: 0; }
.section { page-break-inside: avoid; }
.element-card, .example-box, .styled-table { page-break-inside: auto; }
}
@media (max-width: 768px) {
.document-body { padding: 25px 20px; }
.document-header { padding: 30px 20px; }
.diagram-elements { grid-template-columns: 1fr; }
.header-top { flex-direction: column; gap: 15px; }
.doc-meta { text-align: center; }
}
</style>
</head>
<body>
<div class="document-container">
<header class="document-header">
<div class="header-top">
<div class="company-badge">
<div class="company-logo"></div>
</div>
<div class="doc-meta">
<div>شماره مستند: UML-EDU-1405-001</div>
<div>نسخه: .۰</div>
<div>تاریخ تهیه: ۱۶ شهریور ۱۴۰۵ — ساعت ۱۰:۳۰</div>
<div>طبقه‌بندی: آموزشی / عمومی</div>
</div>
</div>
<div class="document-title">
<span class="label">EDUCATIONAL DOCUMENTATION</span>
<h1>مستند آموزشی جامع نمودارهای UML</h1>
<h2>راهنمای کامل با مثال‌های عملی و کاربردی</h2>
</div>
</header>
<div class="document-body">
<div class="intro-text">
<strong>همکاران گرامی و علاقه‌مندان به مهندسی نرم‌افزار،</strong><br>
این مستند آموزشی به‌عنوان مرجعی جامع برای یادگیری و درک عمیق هشت نمودار اصلی UML (Unified Modeling Language) تهیه شده است. هر فصل به تفصیل یک نوع نمودار را با ذکر المان‌ها، روابط، کاربردها و مثال‌های عملی توضیح می‌دهد. هدف از این مستند، ارائه درکی کاربردی و آماده‌سازی شما برای مدل‌سازی حرفه‌ای سیستم‌های نرم‌افزاری است.
</div>

<div class="toc">
<h4> فهرست مطالب</h4>
<ul>
<li><a href="#section1"><span class="num">۱</span> نمودار مورد کاربرد (Use Case Diagram)</a></li>
<li><a href="#section2"><span class="num">۲</span> نمودار فعالیت (Activity Diagram)</a></li>
<li><a href="#section3"><span class="num">۳</span> نمودار ماشین حالت (State Machine Diagram)</a></li>
<li><a href="#section4"><span class="num">۴</span> نمودار کامپوننت (Component Diagram)</a></li>
<li><a href="#section5"><span class="num">۵</span> نمودار استقرار (Deployment Diagram)</a></li>
<li><a href="#section6"><span class="num"></span> نمودار کلاس (Class Diagram)</a></li>
<li><a href="#section7"><span class="num">۷</span> نمودار توالی (Sequence Diagram)</a></li>
<li><a href="#section8"><span class="num">۸</span> نمودار Swimlane</a></li>
</ul>
</div>

<div id="section1" class="section">
<div class="section-header">
<div class="section-number">۱</div>
<div class="section-title">
<h3>نمودار مورد کاربرد (Use Case Diagram)</h3>
<div class="subtitle">نمایش تعامل کاربران با سیستم</div>
</div>
</div>
<div class="section-content">
<p>نمودار Use Case یکی از پرکاربردترین نمودارهای UML است که برای نمایش تعامل بین کاربران (Actors) و سیستم استفاده می‌شود. این نمودار دیدگاهی بیرونی از سیستم ارائه داده و نیازمندی‌های عملکردی را بدون ورود به جزئیات فنی نشان می‌دهد.</p>

<div class="diagram-elements">
<div class="element-card">
<div class="icon">👤</div>
<h4>Actor (بازیگر)</h4>
<p>نماینده کاربر، سیستم خارجی یا موجودیتی که با سیستم تعامل دارد. به صورت شکل انسان نمایش داده می‌شود.</p>
</div>
<div class="element-card">
<div class="icon">⭕</div>
<h4>Use Case (مورد کاربرد)</h4>
<p>یک عملکرد یا سرویس خاص که سیستم ارائه می‌دهد. به صورت بیضی نمایش داده می‌شود.</p>
</div>
<div class="element-card">
<div class="icon"></div>
<h4>System Boundary (مرز سیستم)</h4>
<p>مستطیلی که محدوده سیستم را مشخص کرده و Use Caseها درون آن قرار می‌گیرند.</p>
</div>
<div class="element-card">
<div class="icon">➖</div>
<h4>Association (ارتباط)</h4>
<p>خط ساده که Actor را به Use Case مرتبط وصل می‌کند.</p>
</div>
</div>

<h4 style="color: var(--primary); margin-top: 25px; font-size: 16px;">انواع روابط در Use Case:</h4>
<ul class="relationships-list">
<li><strong> Include (شامل):</strong> رابطه‌ای که نشان می‌دهد یک Use Case حتماً شامل Use Case دیگری است. با خط‌چین و stereotype <code>&lt;&lt;include&gt;&gt;</code> نمایش داده می‌شود.</li>
<li><strong>🔀 Extend (گسترش):</strong> رابطه‌ای اختیاری که نشان می‌دهد یک Use Case می‌تواند در شرایط خاصی Use Case دیگر را گسترش دهد. با خط‌چین و stereotype <code>&lt;&lt;extend&gt;&gt;</code> نمایش داده می‌شود.</li>
<li><strong>🔺 Generalization (تعمیم):</strong> رابطه ارث‌بری بین Actors یا بین Use Cases. با فلش مثلثی توخالی نمایش داده می‌شود.</li>
</ul>

<div class="example-box">
<h4> مثال عملی: سیستم بانکداری آنلاین</h4>
<p><strong>Actors:</strong> مشتری، کارمند بانک، مدیر سیستم</p>
<p><strong>Use Cases:</strong></p>
<ul>
<li>مشتری → ورود به سیستم، مشاهده موجودی، انتقال وجه، پرداخت قبض</li>
<li>کارمند بانک → ثبت مشتری جدید، تأیید تراکنش‌های بزرگ</li>
<li>مدیر سیستم → مدیریت کاربران، مشاهده گزارش‌ها</li>
<li>«انتقال وجه» <code>&lt;&lt;include&gt;&gt;</code> «بررسی موجودی»</li>
<li>«ورود به سیستم» <code>&lt;&lt;extend&gt;&gt;</code> «احراز هویت دو مرحله‌ای» (در صورت فعال بودن)</li>
</ul>
</div>

<div class="info-box">
<strong>💡 نکته کلیدی:</strong> نمودار Use Case در فاز تحلیل نیازمندی‌ها استفاده شده و به درک مشترک بین تیم فنی و ذینفعان کسب‌وکار کمک می‌کند.
</div>
</div>
</div>

<div id="section2" class="section">
<div class="section-header">
<div class="section-number">۲</div>
<div class="section-title">
<h3>نمودار فعالیت (Activity Diagram)</h3>
<div class="subtitle">مدل‌سازی جریان کار و فرآیندها</div>
</div>
</div>
<div class="section-content">
<p>نمودار Activity برای نمایش جریان کار (Workflow)، فرآیندهای کسب‌وکار و الگوریتم‌ها استفاده می‌شود. این نمودار شبیه فلوچارت است اما قدرت بیشتری در نمایش فعالیت‌های موازی و تصمیم‌گیری‌های پیچیده دارد.</p>

<div class="diagram-elements">
<div class="element-card">
<div class="icon">⚫</div>
<h4>Initial Node (گره شروع)</h4>
<p>دایره توپر سیاه که نقطه شروع فرآیند را نشان می‌دهد.</p>
</div>
<div class="element-card">
<div class="icon">🎯</div>
<h4>Final Node (گره پایان)</h4>
<p>دایره توپر با حاشیه ضخیم که پایان فرآیند را نشان می‌دهد.</p>
</div>
<div class="element-card">
<div class="icon">▭</div>
<h4>Action (فعالیت)</h4>
<p>مستطیل با گوشه‌های گرد که یک عملیات یا کار را نشان می‌دهد.</p>
</div>
<div class="element-card">
<div class="icon">◇</div>
<h4>Decision/Merge (تصمیم/ادغام)</h4>
<p>لوزی که برای انشعاب شرطی یا ادغام جریان‌ها استفاده می‌شود.</p>
</div>
<div class="element-card">
<div class="icon">▬</div>
<h4>Fork/Join (انشعاب/هم‌زمانی)</h4>
<p>خط ضخیم افقی یا عمودی برای نمایش فعالیت‌های موازی.</p>
</div>
<div class="element-card">
<div class="icon">→</div>
<h4>Control Flow (جریان کنترل)</h4>
<p>فلش که ترتیب اجرای فعالیت‌ها را نشان می‌دهد.</p>
</div>
</div>

<div class="example-box">
<h4>📋 مثال عملی: فرآیند ثبت سفارش آنلاین</h4>
<p><strong>جریان فرآیند:</strong></p>
<ul>
<li><strong>شروع:</strong> مشتری وارد سایت می‌شود</li>
<li><strong>فعالیت‌ها:</strong> انتخاب محصول → افزودن به سبد خرید → ورود/ثبت‌نام → انتخاب آدرس → انتخاب روش پرداخت</li>
<li><strong>Decision:</strong> آیا موجودی کافی است؟ (بله/خیر)</li>
<li><strong>Fork:</strong> هم‌زمان: ۱) کاهش موجودی انبار ۲) ارسال ایمیل تأیید</li>
<li><strong>Join:</strong> انتظار برای تکمیل هر دو فعالیت موازی</li>
<li><strong>پایان:</strong> ثبت نهایی سفارش</li>
</ul>
</div>

<div class="highlight-box">
<strong>کاربردهای کلیدی:</strong> مدل‌سازی فرآیندهای کسب‌وکار، الگوریتم‌های پیچیده، گردش کار (Workflow)، و تحلیل سناریوهای مختلف
</div>
</div>
</div>

<div id="section3" class="section">
<div class="section-header">
<div class="section-number">۳</div>
<div class="section-title">
<h3>نمودار ماشین حالت (State Machine Diagram)</h3>
<div class="subtitle">نمایش چرخه حیات و تغییر وضعیت‌ها</div>
</div>
</div>
<div class="section-content">
<p>نمودار State Machine برای نمایش وضعیت‌های مختلف یک شیء و رویدادهایی که باعث تغییر وضعیت می‌شوند استفاده می‌شود. این نمودار برای مدل‌سازی چرخه حیات (Lifecycle) موجودیت‌ها ایده‌آل است.</p>

<div class="diagram-elements">
<div class="element-card">
<div class="icon">⚫</div>
<h4>Initial State (وضعیت شروع)</h4>
<p>دایره توپر کوچک که وضعیت اولیه را نشان می‌دهد.</p>
</div>
<div class="element-card">
<div class="icon"></div>
<h4>Final State (وضعیت پایانی)</h4>
<p>دایره توپر با حاشیه ضخیم که وضعیت نهایی را نشان می‌دهد.</p>
</div>
<div class="element-card">
<div class="icon">🔲</div>
<h4>State (وضعیت)</h4>
<p>مستطیل با گوشه‌های گرد که یک وضعیت خاص را نشان می‌دهد.</p>
</div>
<div class="element-card">
<div class="icon">️</div>
<h4>Transition (گذار)</h4>
<p>فلش که تغییر از یک وضعیت به وضعیت دیگر را نشان می‌دهد. می‌تواند برچسب (رویداد/شرط/اقدام) داشته باشد.</p>
</div>
</div>

<h4 style="color: var(--primary); margin-top: 25px; font-size: 16px;">ساختار Transition:</h4>
<div class="info-box">
<strong>فرمت:</strong> <code>eventName [guardCondition] / action</code><br>
مثال: <code>pay [amount > 0] / updateInventory()</code>
</div>

<div class="example-box">
<h4> مثال عملی: چرخه حیات سفارش</h4>
<p><strong>وضعیت‌ها:</strong></p>
<ul>
<li>ایجاد شده (Created)</li>
<li>در انتظار پرداخت (Pending Payment)</li>
<li>پرداخت شده (Paid)</li>
<li>در حال پردازش (Processing)</li>
<li>ارسال شده (Shipped)</li>
<li>تحویل داده شده (Delivered)</li>
<li>لغو شده (Cancelled)</li>
</ul>
<p><strong>گذارها:</strong></p>
<ul>
<li>Created → Pending Payment: رویداد «ثبت سفارش»</li>
<li>Pending Payment → Paid: رویداد «پرداخت موفق»</li>
<li>Paid → Processing: رویداد «تأیید پرداخت»</li>
<li>Processing → Shipped: رویداد «ارسال کالا»</li>
<li>Shipped → Delivered: رویداد «تحویل به مشتری»</li>
<li>هر وضعیت → Cancelled: رویداد «لغو» (با شرایط خاص)</li>
</ul>
</div>

<div class="warning-box">
<strong>⚠️ نکته مهم:</strong> State Machine برای اشیایی استفاده می‌شود که چرخه حیات مشخص و وضعیت‌های گسسته دارند. برای فرآیندهای پیچیده با فعالیت‌های موازی، از Activity Diagram استفاده کنید.
</div>
</div>
</div>

<div id="section4" class="section">
<div class="section-header">
<div class="section-number">۴</div>
<div class="section-title">
<h3>نمودار کامپوننت (Component Diagram)</h3>
<div class="subtitle">نمایش ساختار ماژولار و وابستگی‌ها</div>
</div>
</div>
<div class="section-content">
<p>نمودار Component برای نمایش ساختار فیزیکی سیستم، ماژول‌ها، کتابخانه‌ها و وابستگی‌های بین آن‌ها استفاده می‌شود. این نمودار دیدگاهی از معماری نرم‌افزار در سطح کامپوننت ارائه می‌دهد.</p>

<div class="diagram-elements">
<div class="element-card">
<div class="icon">🧩</div>
<h4>Component (کامپوننت)</h4>
<p>مستطیل با آیکون کامپوننت (دو مستطیل کوچک در گوشه) که یک ماژول یا جزء سیستم را نشان می‌دهد.</p>
</div>
<div class="element-card">
<div class="icon">🍭</div>
<h4>Interface Provided (اینترفیس ارائه‌شده)</h4>
<p>دایره (Lollipop) که سرویس‌هایی که کامپوننت ارائه می‌دهد را نشان می‌دهد.</p>
</div>
<div class="element-card">
<div class="icon">◗</div>
<h4>Interface Required (اینترفیس مورد نیاز)</h4>
<p>نیم‌دایره (Socket) که سرویس‌هایی که کامپوننت نیاز دارد را نشان می‌دهد.</p>
</div>
<div class="element-card">
<div class="icon">📁</div>
<h4>Package (بسته)</h4>
<p>پوشه‌ای که کامپوننت‌های مرتبط را گروه‌بندی می‌کند.</p>
</div>
<div class="element-card">
<div class="icon">⇢</div>
<h4>Dependency (وابستگی)</h4>
<p>فلش خط‌چین که وابستگی بین کامپوننت‌ها را نشان می‌دهد.</p>
</div>
<div class="element-card">
<div class="icon">—</div>
<h4>Connector (اتصال)</h4>
<p>خط جامد که اتصال بین Interface Provided و Required را نشان می‌دهد.</p>
</div>
</div>

<div class="example-box">
<h4>📋 مثال عملی: سیستم مدیریت محتوا (CMS)</h4>
<p><strong>کامپوننت‌ها:</strong></p>
<ul>
<li><strong>User Management:</strong> مدیریت کاربران و احراز هویت</li>
<li><strong>Content Engine:</strong> مدیریت محتوا و ویرایشگر</li>
<li><strong>Media Library:</strong> مدیریت فایل‌های چندرسانه‌ای</li>
<li><strong>Search Module:</strong> موتور جستجو</li>
<li><strong>Notification Service:</strong> ارسال اعلان‌ها</li>
</ul>
<p><strong>وابستگی‌ها:</strong></p>
<ul>
<li>Content Engine → User Management (برای بررسی مجوزها)</li>
<li>Search Module → Content Engine (برای ایندکس محتوا)</li>
<li>Notification Service → User Management (برای دریافت اطلاعات کاربران)</li>
</ul>
<p><strong>اینترفیس‌ها:</strong></p>
<ul>
<li>User Management: <code>IAuthentication</code>, <code>IUserProfile</code></li>
<li>Content Engine: <code>IContentCRUD</code>, <code>IVersioning</code></li>
</ul>
</div>

<div class="success-box">
<strong>✓ مزایا:</strong> درک معماری سیستم، مدیریت وابستگی‌ها، برنامه‌ریزی برای ماژولاریزاسیون، و تسهیل توسعه موازی تیم‌ها
</div>
</div>
</div>

<div id="section5" class="section">
<div class="section-header">
<div class="section-number">۵</div>
<div class="section-title">
<h3>نمودار استقرار (Deployment Diagram)</h3>
<div class="subtitle">نمایش معماری فیزیکی و توزیع نرم‌افزار</div>
</div>
</div>
<div class="section-content">
<p>نمودار Deployment برای نمایش سخت‌افزارها، نودها، محیط‌های اجرا و نحوه استقرار نرم‌افزار بر روی زیرساخت فیزیکی استفاده می‌شود. این نمودار دیدگاهی از معماری فیزیکی سیستم ارائه می‌دهد.</p>

<div class="diagram-elements">
<div class="element-card">
<div class="icon">🖥️</div>
<h4>Node (گره)</h4>
<p>مکعب سه‌بعدی که یک سخت‌افزار یا محیط اجرا (مانند سرور، کامپیوتر، کانتینر) را نشان می‌دهد.</p>
</div>
<div class="element-card">
<div class="icon">💻</div>
<h4>Device (دستگاه)</h4>
<p>نود فیزیکی مانند سرور، روتر، یا دستگاه موبایل.</p>
</div>
<div class="element-card">
<div class="icon">⚙️</div>
<h4>Execution Environment (محیط اجرا)</h4>
<p>نود نرم‌افزاری مانند OS، JVM، Docker، یا Kubernetes.</p>
</div>
<div class="element-card">
<div class="icon">📄</div>
<h4>Artifact (مصنوع)</h4>
<p>فایل‌های اجرایی، کتابخانه‌ها، پایگاه داده، یا فایل‌های پیکربندی که بر روی نودها استقرار می‌یابند.</p>
</div>
<div class="element-card">
<div class="icon">⇌</div>
<h4>Communication Path (مسیر ارتباطی)</h4>
<p>خط جامد که ارتباط فیزیکی بین نودها را نشان می‌دهد.</p>
</div>
<div class="element-card">
<div class="icon">⇣</div>
<h4>Deployment Relationship (رابطه استقرار)</h4>
<p>فلش خط‌چین که استقرار Artifact بر روی Node را نشان می‌دهد.</p>
</div>
</div>

<div class="example-box">
<h4>📋 مثال عملی: سیستم وب اپلیکیشن میکروسرویس</h4>
<p><strong>نودها:</strong></p>
<ul>
<li><strong>Load Balancer:</strong> Nginx (Device)</li>
<li><strong>Application Servers:</strong> Kubernetes Cluster (Execution Environment)
<ul>
<li>User Service Pod</li>
<li>Order Service Pod</li>
<li>Payment Service Pod</li>
</ul>
</li>
<li><strong>Database Server:</strong> PostgreSQL Cluster (Device)</li>
<li><strong>Cache Server:</strong> Redis Cluster (Device)</li>
<li><strong>Message Broker:</strong> RabbitMQ Server (Device)</li>
</ul>
<p><strong>Artifacts:</strong></p>
<ul>
<li>User Service → user-service.jar</li>
<li>Order Service → order-service.jar</li>
<li>Database → schema.sql, data.migration</li>
</ul>
<p><strong>ارتباطات:</strong></p>
<ul>
<li>Load Balancer ⇌ Application Servers (HTTP/HTTPS)</li>
<li>Application Servers  Database Server (JDBC)</li>
<li>Application Servers ⇌ Cache Server (Redis Protocol)</li>
<li>Application Servers ⇌ Message Broker (AMQP)</li>
</ul>
</div>

<div class="info-box">
<strong>💡 کاربرد:</strong> برنامه‌ریزی زیرساخت، مستندسازی محیط‌های Development/Staging/Production، و تحلیل Performance و Scalability
</div>
</div>
</div>

<div id="section6" class="section">
<div class="section-header">
<div class="section-number">۶</div>
<div class="section-title">
<h3>نمودار کلاس (Class Diagram)</h3>
<div class="subtitle">نمایش ساختار ایستایی و روابط بین کلاس‌ها</div>
</div>
</div>
<div class="section-content">
<p>نمودار Class مهم‌ترین و پرکاربردترین نمودار UML است که ساختار ایستایی (Static Structure) سیستم را نشان می‌دهد. این نمودار کلاس‌ها، ویژگی‌ها، متدها و روابط بین آن‌ها را مدل‌سازی می‌کند.</p>

<div class="diagram-elements">
<div class="element-card">
<div class="icon">🗂️</div>
<h4>Class (کلاس)</h4>
<p>مستطیل سه‌بخشی: ۱) نام کلاس ۲) Attributes (ویژگی‌ها) ۳) Methods (متدها)</p>
</div>
<div class="element-card">
<div class="icon">◯</div>
<h4>Interface (اینترفیس)</h4>
<p>دایره یا مستطیل با stereotype <code>&lt;&lt;interface&gt;&gt;</code> که قراردادها را تعریف می‌کند.</p>
</div>
<div class="element-card">
<div class="icon">🗂️</div>
<h4>Abstract Class (کلاس انتزاعی)</h4>
<p>کلاس با نام ایتالیک که نمی‌توان نمونه مستقیم از آن ساخت.</p>
</div>
<div class="element-card">
<div class="icon">➖</div>
<h4>Association (ارتباط)</h4>
<p>خط جامد که ارتباط بین دو کلاس را نشان می‌دهد. می‌تواند Multiplicity (تعداد) داشته باشد.</p>
</div>
<div class="element-card">
<div class="icon">◇</div>
<h4>Aggregation (تجميع)</h4>
<p>لوزی توخالی که رابطه «has-a» ضعیف را نشان می‌دهد (جزء می‌تواند مستقل وجود داشته باشد).</p>
</div>
<div class="element-card">
<div class="icon">◆</div>
<h4>Composition (ترکیب)</h4>
<p>لوزی توپر که رابطه «has-a» قوی را نشان می‌دهد (جزء بدون کل نمی‌تواند وجود داشته باشد).</p>
</div>
<div class="element-card">
<div class="icon">△</div>
<h4>Inheritance (ارث‌بری)</h4>
<p>فلش مثلثی توخالی که رابطه «is-a» یا Generalization را نشان می‌دهد.</p>
</div>
<div class="element-card">
<div class="icon">⇢</div>
<h4>Dependency (وابستگی)</h4>
<p>فلش خط‌چین که استفاده موقت یک کلاس از کلاس دیگر را نشان می‌دهد.</p>
</div>
</div>

<h4 style="color: var(--primary); margin-top: 25px; font-size: 16px;">Visibility (سطح دسترسی):</h4>
<div class="info-box">
<code>+ public</code> | <code>- private</code> | <code># protected</code> | <code>~ package</code>
</div>

<div class="example-box">
<h4>📋 مثال عملی: سیستم مدیریت کتابخانه</h4>
<p><strong>کلاس‌ها:</strong></p>
<ul>
<li><strong>Book:</strong>
<ul>
<li>Attributes: -isbn: String, -title: String, -author: String, -isAvailable: Boolean</li>
<li>Methods: +borrow(), +return(), +reserve()</li>
</ul>
</li>
<li><strong>Member:</strong>
<ul>
<li>Attributes: -memberId: String, -name: String, -email: String</li>
<li>Methods: +searchBook(), +borrowBook(), +returnBook()</li>
</ul>
</li>
<li><strong>Loan:</strong>
<ul>
<li>Attributes: -loanId: String, -loanDate: Date, -dueDate: Date</li>
<li>Methods: +calculateFine(), +extendLoan()</li>
</ul>
</li>
<li><strong>Librarian:</strong> (ارث‌بری از Member)
<ul>
<li>Methods: +addBook(), +removeBook(), +generateReport()</li>
</ul>
</li>
</ul>
<p><strong>روابط:</strong></p>
<ul>
<li>Member «1» — «*» Loan (یک عضو می‌تواند چندین امانی داشته باشد)</li>
<li>Book «1» — «*» Loan (یک کتاب می‌تواند در چندین امانی باشد)</li>
<li>Loan ◆ Book (Composition: امانی بدون کتاب معنی ندارد)</li>
<li>Librarian △ Member (ارث‌بری)</li>
<li>Book  Notification (وابستگی برای ارسال اعلان)</li>
</ul>
</div>

<div class="highlight-box">
<strong> نکته طلایی:</strong> نمودار Class پایه و اساس طراحی شیءگرا است و مستقیماً به کد تبدیل می‌شود. طراحی دقیق این نمودار، کیفیت کد نهایی را تضمین می‌کند.
</div>
</div>
</div>

<div id="section7" class="section">
<div class="section-header">
<div class="section-number">۷</div>
<div class="section-title">
<h3>نمودار توالی (Sequence Diagram)</h3>
<div class="subtitle">نمایش تعاملات زمانی بین اشیاء</div>
</div>
</div>
<div class="section-content">
<p>نمودار Sequence برای نمایش تعاملات بین اشیاء در طول زمان استفاده می‌شود. این نمودار نشان می‌دهد که پیام‌ها به چه ترتیبی بین اشیاء مبادله می‌شوند و برای درک سناریوهای اجرایی ایده‌آل است.</p>

<div class="diagram-elements">
<div class="element-card">
<div class="icon">▭</div>
<h4>Participant/Lifeline (خط حیات)</h4>
<p>مستطیل در بالا با خط‌چین عمودی که حضور یک شیء در طول زمان را نشان می‌دهد.</p>
</div>
<div class="element-card">
<div class="icon">▮</div>
<h4>Activation (فعال‌سازی)</h4>
<p>مستطیل باریک روی Lifeline که مدت زمان اجرای یک متد را نشان می‌دهد.</p>
</div>
<div class="element-card">
<div class="icon">→●</div>
<h4>Synchronous Message (پیام هم‌زمان)</h4>
<p>فلش جامد که فراخوانی متد را نشان می‌دهد (فرستنده منتظر پاسخ می‌ماند).</p>
</div>
<div class="element-card">
<div class="icon">→○</div>
<h4>Asynchronous Message (پیام ناهم‌زمان)</h4>
<p>فلش خط‌چین که ارسال پیام بدون انتظار برای پاسخ را نشان می‌دهد.</p>
</div>
<div class="element-card">
<div class="icon">⇠</div>
<h4>Return Message (پیام بازگشت)</h4>
<p>فلش خط‌چین که مقدار بازگشتی یا پایان اجرا را نشان می‌دهد.</p>
</div>
<div class="element-card">
<div class="icon">↷</div>
<h4>Self Message (پیام خودی)</h4>
<p>فلش منحنی که فراخوانی متد روی خود شیء را نشان می‌دهد.</p>
</div>
<div class="element-card">
<div class="icon">▣</div>
<h4>Combined Fragment (قطعه ترکیبی)</h4>
<p>مستطیل با برچسب (alt, opt, loop, break) برای نمایش ساختارهای کنترلی.</p>
</div>
</div>

<h4 style="color: var(--primary); margin-top: 25px; font-size: 16px;">انواع Combined Fragments:</h4>
<ul class="relationships-list">
<li><strong>alt (شرطی):</strong> معادل if-else (چندین گزینه با شرایط مختلف)</li>
<li><strong>opt (اختیاری):</strong> معادل if (یک گزینه در صورت برقراری شرط)</li>
<li><strong>loop (حلقه):</strong> معادل for/while (تکرار چندین بار)</li>
<li><strong>break (شکست):</strong> معادل break (خروج از سناریو در صورت خطا)</li>
</ul>

<div class="example-box">
<h4>📋 مثال عملی: فرآیند خرید آنلاین</h4>
<p><strong>Participants:</strong> Customer, ShoppingCart, PaymentGateway, Inventory, EmailService</p>
<p><strong>ترتیب پیام‌ها:</strong></p>
<ol>
<li>Customer → ShoppingCart: addItem(product)</li>
<li>Customer → ShoppingCart: checkout()</li>
<li>ShoppingCart → Inventory: checkAvailability(product)</li>
<li>Inventory → ShoppingCart: return true</li>
<li>ShoppingCart → PaymentGateway: processPayment(amount)</li>
<li><strong>alt [payment success]:</strong>
<ul>
<li>PaymentGateway → ShoppingCart: paymentConfirmed()</li>
<li>ShoppingCart → Inventory: reduceStock(product)</li>
<li>ShoppingCart → EmailService: sendConfirmation(customerEmail)</li>
<li>EmailService → Customer: send email</li>
</ul>
</li>
<li><strong>else [payment failed]:</strong>
<ul>
<li>PaymentGateway → ShoppingCart: paymentFailed()</li>
<li>ShoppingCart → Customer: showError()</li>
</ul>
</li>
</ol>
</div>

<div class="success-box">
<strong>✓ کاربرد:</strong> درک سناریوهای پیچیده، دیباگ کردن، مستندسازی APIها، و ارتباط بین تیم‌های Frontend و Backend
</div>
</div>
</div>

<div id="section8" class="section">
<div class="section-header">
<div class="section-number">۸</div>
<div class="section-title">
<h3>نمودار Swimlane</h3>
<div class="subtitle">نمایش مسئولیت‌ها در فرآیندها</div>
</div>
</div>
<div class="section-content">
<p>نمودار Swimlane (یا Activity Diagram با Swimlanes) نسخه پیشرفته‌ای از Activity Diagram است که فعالیت‌ها را بر اساس مسئولیت (Actor، Department، یا System) در Laneهای جداگانه سازماندهی می‌کند. این نمودار برای نمایش «چه کسی چه کاری انجام می‌دهد» عالی است.</p>

<div class="diagram-elements">
<div class="element-card">
<div class="icon">▤</div>
<h4>Swimlane (لاین شنا)</h4>
<p>ناحیه افقی یا عمودی که تمام فعالیت‌های یک مسئول یا بخش را در خود جای می‌دهد.</p>
</div>
<div class="element-card">
<div class="icon">▭</div>
<h4>Action (فعالیت)</h4>
<p>مستطیل با گوشه‌های گرد که یک کار را در Swimlane مربوطه نشان می‌دهد.</p>
</div>
<div class="element-card">
<div class="icon">◇</div>
<h4>Decision (تصمیم)</h4>
<p>لوزی که می‌تواند بین Swimlane‌ها جریان ایجاد کند.</p>
</div>
<div class="element-card">
<div class="icon">→</div>
<h4>Cross-Lane Flow (جریان بین لاین‌ها)</h4>
<p>فلش که انتقال کار از یک مسئول به مسئول دیگر را نشان می‌دهد.</p>
</div>
</div>

<div class="example-box">
<h4>📋 مثال عملی: فرآیند استخدام نیروی جدید</h4>
<p><strong>Swimlanes:</strong> متقاضی، منابع انسانی (HR)، مدیر فنی، مدیرعامل</p>
<p><strong>جریان فرآیند:</strong></p>
<ul>
<li><strong>متقاضی:</strong>
<ul>
<li>ارسال رزومه → HR</li>
<li>شرکت در مصاحبه فنی</li>
<li>دریافت پیشنهاد کاری</li>
</ul>
</li>
<li><strong>HR:</strong>
<ul>
<li>بررسی اولیه رزومه</li>
<li>هماهنگی مصاحبه → مدیر فنی</li>
<li>بررسی سوابق (Background Check)</li>
<li>ارسال پیشنهاد کاری (در صورت تأیید مدیرعامل)</li>
</ul>
</li>
<li><strong>مدیر فنی:</strong>
<ul>
<li>مصاحبه فنی</li>
<li>ارزیابی مهارت‌ها</li>
<li>ارسال نتیجه → HR</li>
</ul>
</li>
<li><strong>مدیرعامل:</strong>
<ul>
<li>تأیید نهایی استخدام (Decision)</li>
</ul>
</li>
</ul>
<p><strong>Decision Points:</strong></p>
<ul>
<li>آیا رزومه مناسب است؟ (HR) → خیر: رد درخواست | بله: ادامه</li>
<li>آیا مصاحبه فنی موفق بود؟ (مدیر فنی) → خیر: رد درخواست | بله: ادامه</li>
<li>آیا مدیرعامل تأیید می‌کند؟ → خیر: رد درخواست | بله: ارسال پیشنهاد</li>
</ul>
</div>

<div class="info-box">
<strong>💡 مزیت کلیدی:</strong> Swimlane Diagram به وضوح نشان می‌دهد که هر Actor یا بخش چه مسئولیتی دارد و کجاها نیاز به هماهنگی و Handoff وجود دارد. این نمودار برای بهینه‌سازی فرآیندهای کسب‌وکار و شناسایی گلوگاه‌ها عالی است.
</div>

<div class="highlight-box">
<strong>🎯 تفاوت با Activity Diagram ساده:</strong> Activity Diagram فقط «چه کاری» را نشان می‌دهد، اما Swimlane Diagram «چه کسی چه کاری» را مشخص می‌کند.
</div>
</div>
</div>

<div class="section">
<div class="section-header">
<div class="section-number">۹</div>
<div class="section-title">
<h3>جمع‌بندی و نکات پایانی</h3>
<div class="subtitle">انتخاب نمودار مناسب برای هر سناریو</div>
</div>
</div>
<div class="section-content">
<p>هر یک از نمودارهای UML هدف خاصی دارند و در فازهای مختلف چرخه حیات توسعه نرم‌افزار استفاده می‌شوند:</p>

<table class="styled-table">
<thead>
<tr>
<th>نمودار</th>
<th>هدف اصلی</th>
<th>فاز استفاده</th>
<th>سطح</th>
</tr>
</thead>
<tbody>
<tr>
<td>Use Case</td>
<td>نیازمندی‌ها و تعامل کاربران</td>
<td>تحلیل</td>
<td>بالا (Business)</td>
</tr>
<tr>
<td>Activity</td>
<td>فرآیندها و الگوریتم‌ها</td>
<td>تحلیل/طراحی</td>
<td>متوسط</td>
</tr>
<tr>
<td>State Machine</td>
<td>چرخه حیات اشیاء</td>
<td>طراحی</td>
<td>پایین (Technical)</td>
</tr>
<tr>
<td>Class</td>
<td>ساختار ایستایی سیستم</td>
<td>طراحی</td>
<td>پایین (Technical)</td>
</tr>
<tr>
<td>Sequence</td>
<td>تعاملات زمانی بین اشیاء</td>
<td>طراحی</td>
<td>پایین (Technical)</td>
</tr>
<tr>
<td>Component</td>
<td>معماری ماژولار</td>
<td>طراحی معماری</td>
<td>بالا (Architecture)</td>
</tr>
<tr>
<td>Deployment</td>
<td>زیرساخت فیزیکی</td>
<td>استقرار</td>
<td>بالا (Infrastructure)</td>
</tr>
<tr>
<td>Swimlane</td>
<td>مسئولیت‌ها در فرآیندها</td>
<td>تحلیل کسب‌وکار</td>
<td>بالا (Business)</td>
</tr>
</tbody>
</table>

<div class="conclusion-box">
<h3>🎯 توصیه‌های کلیدی</h3>
<p>۱. <strong>از ساده شروع کنید:</strong> ابتدا Use Case و Activity Diagram برای درک نیازمندی‌ها</p>
<p>۲. <strong>سپس به جزئیات بروید:</strong> Class و Sequence Diagram برای طراحی فنی</p>
<p>۳. <strong>مستندسازی زنده:</strong> نمودارها را هم‌زمان با توسعه به‌روز نگه دارید</p>
<p>۴. <strong>ابزار مناسب:</strong> از ابزارهایی مانند ابزار UML استفاده کنید که امکان ویرایش آسان و Export به فرمت‌های مختلف را فراهم می‌کنند</p>
<div class="highlight">
<strong>💎 نتیجه نهایی:</strong> تسلط بر UML نه تنها مهارت مدل‌سازی شما را ارتقا می‌دهد، بلکه ارتباط مؤثر با ذینفعان، کاهش خطاهای طراحی و افزایش کیفیت نرم‌افزار را به همراه خواهد داشت.
</div>
</div>

<div class="info-box">
<strong>📚 منابع پیشنهادی برای مطالعه بیشتر:</strong>
<ul style="padding-right: 20px; margin-top: 10px;">
<li>کتاب «UML Distilled» توسط Martin Fowler</li>
<li>استاندارد OMG UML 2.5</li>
<li>الگوهای طراحی (Design Patterns) توسط Gang of Four</li>
</ul>
</div>
</div>
</div>

<div class="page-info">
این مستند آموزشی صرفاً جهت استفاده داخلی و آموزشی تهیه شده است. هرگونه کپی‌برداری یا افشا بدون اجازه کتبی ممنوع است.
</div>
</div>
<footer class="document-footer">
<div class="footer-brand">طراحی، تحلیل و توسعه: هادی خزاعی اصل</div>
<div class="footer-divider"></div>
<div>تاریخ تهیه: ۱۶ شهریور ۱۴۰۵ — ساعت ۱:۳۰ &nbsp;|&nbsp; نسخه: ۱.۰ &nbsp;|&nbsp; طبقه‌بندی: آموزشی / عمومی</div>
</footer>
</div>
</body>
</html>

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Resources\Tools\Document\xUMLDrawer_Technical_Documentation.html

<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>مستند فنی پروژه | ابزار رسم نمودارهای UML</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap');
:root {
--primary: #1a365d;
--primary-light: #2c5282;
--accent: #c9a961;
--accent-dark: #a8893f;
--bg: #faf9f6;
--card-bg: #ffffff;
--text: #1a202c;
--text-muted: #4a5568;
--border: #e2e8f0;
--success: #38a169;
--danger: #e53e3e;
--warning: #dd6b20;
--shadow: 0 4px 6px rgba(0,0,0,0.05), 0 10px 30px rgba(26,54,93,0.08);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Vazirmatn', Tahoma, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.9;
font-size: 15px;
padding: 30px 15px;
}
.document-container {
max-width: 950px;
margin: 0 auto;
background: var(--card-bg);
box-shadow: var(--shadow);
border-radius: 12px;
overflow: hidden;
}
.document-header {
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);
color: white;
padding: 50px 40px 40px;
position: relative;
border-bottom: 4px solid var(--accent);
}
.document-header::before {
content: '';
position: absolute;
top: 0; right: 0;
width: 200px; height: 200px;
background: radial-gradient(circle, rgba(201,169,97,0.15) 0%, transparent 70%);
border-radius: 50%;
transform: translate(50%, -50%);
}
.header-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
position: relative;
z-index: 1;
}
.company-badge {
display: flex;
align-items: center;
gap: 12px;
}
.company-logo {
width: 60px; height: 60px;
background: var(--accent);
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
.doc-meta {
text-align: left;
font-size: 12px;
opacity: 0.9;
}
.doc-meta div { margin-bottom: 3px; }
.document-title {
text-align: center;
position: relative;
z-index: 1;
padding: 20px 0;
}
.document-title .label {
display: inline-block;
background: rgba(201,169,97,0.2);
color: var(--accent);
padding: 4px 16px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
margin-bottom: 15px;
border: 1px solid rgba(201,169,97,0.4);
}
.document-title h1 {
font-size: 28px;
font-weight: 800;
margin-bottom: 10px;
letter-spacing: -0.5px;
}
.document-title h2 {
font-size: 16px;
font-weight: 400;
opacity: 0.9;
}
.document-body {
padding: 40px;
}
.intro-text {
background: linear-gradient(to left, #f7fafc, #edf2f7);
border-right: 4px solid var(--accent);
padding: 20px 25px;
border-radius: 8px;
margin-bottom: 35px;
font-size: 14px;
color: var(--text-muted);
}
.section {
margin-bottom: 40px;
page-break-inside: avoid;
}
.section-header {
display: flex;
align-items: center;
gap: 15px;
padding-bottom: 12px;
border-bottom: 2px solid var(--border);
margin-bottom: 20px;
}
.section-number {
width: 50px; height: 50px;
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 800;
font-size: 20px;
flex-shrink: 0;
box-shadow: 0 4px 10px rgba(26,54,93,0.2);
}
.section-title { flex: 1; }
.section-title h3 {
font-size: 19px;
font-weight: 700;
color: var(--primary);
margin-bottom: 2px;
}
.section-title .subtitle {
font-size: 12px;
color: var(--text-muted);
}
.section-content {
padding-right: 10px;
}
.section-content p {
margin-bottom: 12px;
text-align: justify;
text-indent: 20px;
}
.section-content p.no-indent { text-indent: 0; }
.highlight-box {
background: linear-gradient(to left, #fffaf0, #fef5e7);
border: 1px solid #f6e05e;
border-right: 4px solid var(--accent);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.highlight-box strong {
color: var(--accent-dark);
}
.info-box {
background: linear-gradient(to left, #ebf8ff, #e6f6ff);
border: 1px solid #90cdf4;
border-right: 4px solid var(--primary-light);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.success-box {
background: linear-gradient(to left, #f0fff4, #e6fffa);
border: 1px solid #9ae6b4;
border-right: 4px solid var(--success);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.warning-box {
background: linear-gradient(to left, #fffaf0, #fef5e7);
border: 1px solid #fbd38d;
border-right: 4px solid var(--warning);
padding: 18px 22px;
border-radius: 8px;
margin: 20px 0;
font-size: 14px;
}
.styled-table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
}
.styled-table thead {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
}
.styled-table th {
padding: 14px 16px;
text-align: right;
font-weight: 600;
font-size: 13px;
}
.styled-table td {
padding: 13px 16px;
border-bottom: 1px solid var(--border);
font-size: 13px;
}
.styled-table tbody tr:last-child td { border-bottom: none; }
.styled-table tbody tr:nth-child(even) { background: #f8fafc; }
.styled-table .highlight-row {
background: #fffaf0 !important;
font-weight: 600;
}
.code-block {
background: #1a202c;
color: #e2e8f0;
padding: 18px 22px;
border-radius: 8px;
margin: 15px 0;
font-family: 'Consolas', 'Courier New', monospace;
font-size: 13px;
line-height: 1.7;
direction: ltr;
text-align: left;
overflow-x: auto;
border-right: 4px solid var(--accent);
}
.code-block .keyword { color: #f6ad55; }
.code-block .string { color: #9ae6b4; }
.code-block .comment { color: #718096; font-style: italic; }
.code-block .func { color: #90cdf4; }
.stats-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
margin: 25px 0;
}
.stat-card {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
padding: 20px;
border-radius: 10px;
text-align: center;
box-shadow: 0 4px 12px rgba(26,54,93,0.2);
}
.stat-card .stat-value {
font-size: 24px;
font-weight: 800;
margin-bottom: 5px;
color: var(--accent);
line-height: 1.3;
}
.stat-card .stat-label {
font-size: 12px;
opacity: 0.9;
}
.diagram-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
margin: 25px 0;
}
.diagram-card {
background: #f8fafc;
border: 2px solid var(--border);
border-radius: 10px;
padding: 18px;
transition: all 0.2s;
}
.diagram-card:hover {
border-color: var(--primary-light);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(26,54,93,0.1);
}
.diagram-card .icon {
font-size: 28px;
margin-bottom: 8px;
}
.diagram-card h4 {
font-size: 15px;
font-weight: 700;
color: var(--primary);
margin-bottom: 6px;
}
.diagram-card p {
font-size: 12px;
color: var(--text-muted);
text-indent: 0 !important;
margin-bottom: 0 !important;
}
.module-list {
list-style: none;
padding: 0;
margin: 15px 0;
}
.module-list li {
background: #f8fafc;
border-right: 3px solid var(--primary-light);
padding: 12px 18px;
margin-bottom: 10px;
border-radius: 6px;
font-size: 13px;
}
.module-list li strong {
color: var(--primary);
display: inline-block;
margin-left: 8px;
}
.conclusion-box {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
padding: 30px;
border-radius: 12px;
margin: 30px 0;
text-align: center;
box-shadow: 0 8px 20px rgba(26,54,93,0.3);
}
.conclusion-box h3 {
font-size: 22px;
font-weight: 800;
margin-bottom: 15px;
color: var(--accent);
}
.conclusion-box p {
font-size: 14px;
line-height: 1.8;
text-indent: 0 !important;
margin-bottom: 15px;
}
.conclusion-box .highlight {
background: rgba(201,169,97,0.2);
padding: 15px 20px;
border-radius: 8px;
margin-top: 20px;
border: 1px solid rgba(201,169,97,0.4);
}
.document-footer {
background: var(--primary);
color: white;
padding: 25px 40px;
text-align: center;
font-size: 12px;
margin-top: 40px;
}
.document-footer .footer-brand {
font-weight: 700;
color: var(--accent);
margin-bottom: 5px;
font-size: 14px;
}
.footer-divider {
width: 60px;
height: 2px;
background: var(--accent);
margin: 10px auto;
}
.page-info {
text-align: center;
padding: 15px;
font-size: 11px;
color: var(--text-muted);
border-top: 1px solid var(--border);
margin-top: 30px;
}
@media print {
body { background: white; padding: 0; }
.document-container { box-shadow: none; border-radius: 0; }
.section { page-break-inside: avoid; }
}
@media (max-width: 768px) {
.document-body { padding: 25px 20px; }
.document-header { padding: 30px 20px; }
.diagram-grid { grid-template-columns: 1fr; }
.stats-grid { grid-template-columns: 1fr; }
.header-top { flex-direction: column; gap: 15px; }
.doc-meta { text-align: center; }
}
</style>
</head>
<body>
<div class="document-container">
<header class="document-header">
<div class="header-top">
<div class="company-badge">
<div class="company-logo"></div>
</div>
<div class="doc-meta">
<div>شماره مستند: UML-TOOL-DOC-1405-001</div>
<div>نسخه: ۱.۰</div>
<div>تاریخ تهیه: ۱۶ شهریور ۱۴۰۵ — ساعت ۱۰:۳۰</div>
<div>طبقه‌بندی: محرمانه / فنی</div>
</div>
</div>
<div class="document-title">
<span class="label">TECHNICAL DOCUMENTATION</span>
<h1>مستند فنی پروژه ابزار رسم نمودارهای UML</h1>
<h2>تحلیل معماری، ماژول‌ها، موتور رسم و ساختار داده</h2>
</div>
</header>
<div class="document-body">
<div class="intro-text">
<strong>همکاران فنی و ذینفعان گرامی،</strong><br>
این مستند به‌عنوان مرجع فنی جامع پروژه «ابزار رسم نمودارهای UML» تهیه شده است. هدف از این مستند، تشریح دقیق معماری، ساختار کد، ماژول‌های کلیدی، الگوهای طراحی به‌کار رفته و قابلیت‌های فنی این ابزار می‌باشد تا تیم‌های توسعه و نگهداری بتوانند با دیدی کامل و یکپارچه، به تحلیل، توسعه و گسترش آن بپردازند.
</div>

<div class="section">
<div class="section-header">
<div class="section-number">۱</div>
<div class="section-title">
<h3>خلاصه اجرایی پروژه</h3>
<div class="subtitle">Executive Summary</div>
</div>
</div>
<div class="section-content">
<p>پروژه «ابزار رسم نمودارهای UML» یک اپلیکیشن تحت وب کاملاً سمت کلاینت (Client-Side) است که با استفاده از تکنولوژی‌های HTML5، CSS3 و JavaScript خالص (Vanilla JS) توسعه یافته است. این ابزار بدون نیاز به هیچ‌گونه Backend یا فریم‌ورک خارجی، امکان رسم، ویرایش، ذخیره و بازیابی هشت نوع نمودار استاندارد UML را در محیطی تعاملی و حرفه‌ای فراهم می‌آورد.</p>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value">۸</div>
<div class="stat-label">نوع نمودار UML پشتیبانی شده</div>
</div>
<div class="stat-card">
<div class="stat-value">۵۰+</div>
<div class="stat-label">المان و رابطه قابل رسم</div>
</div>
<div class="stat-card">
<div class="stat-value">۱۰۰٪</div>
<div class="stat-label">اجرا در سمت مرورگر (بدون Backend)</div>
</div>
</div>
<div class="highlight-box">
<strong>💎 ارزش کلیدی:</strong> این ابزار با تکیه بر HTML5 Canvas API و معماری ماژولار، یک محیط حرفه‌ای، سبک و کاملاً آفلاین برای مدل‌سازی سیستم‌های نرم‌افزاری در اختیار معماران و تحلیل‌گران قرار می‌دهد.
</div>
</div>
</div>

<div class="section">
<div class="section-header">
<div class="section-number">۲</div>
<div class="section-title">
<h3>معماری کلی سیستم</h3>
<div class="subtitle">System Architecture Overview</div>
</div>
</div>
<div class="section-content">
<p>معماری این پروژه بر پایه الگوی <strong>Single-Page Application (SPA)</strong> و با رویکرد <strong>State-Centric</strong> طراحی شده است. تمامی وضعیت‌های برنامه در یک آبجکت مرکزی <code>state</code> نگهداری می‌شوند و هر تغییر در state منجر به بازترسیم کامل (Redraw) بوم می‌گردد.</p>
<ul class="module-list">
<li><strong>🎨 Rendering Layer:</strong> موتور رسم مبتنی بر HTML5 Canvas 2D Context با قابلیت پشتیبانی از تم روشن و تاریک.</li>
<li><strong>🧠 State Management:</strong> یک آبجکت سراسری <code>state</code> که شامل لیست نمودارها، نمودار فعال، ابزار جاری، وضعیت Drag &amp; Draw و تنظیمات UI است.</li>
<li><strong>🖱️ Interaction Layer:</strong> مدیریت رویدادهای Mouse (mousedown, mousemove, mouseup, dblclick) و Keyboard برای تعامل با المان‌ها.</li>
<li><strong>💾 Persistence Layer:</strong> سیستم ذخیره‌سازی مبتنی بر JSON File API (بدون نیاز به LocalStorage یا Backend).</li>
<li><strong>🎛️ UI Layer:</strong> رابط کاربری مبتنی بر Sidebar، Tab Bar، Modal ها و Properties Panel.</li>
</ul>
<div class="code-block">
<span class="comment">// ساختار کلی State Object</span><br>
<span class="keyword">const</span> state = {<br>
&nbsp;&nbsp;diagrams: [],&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// لیست همه نمودارها</span><br>
&nbsp;&nbsp;activeDiagramId: <span class="keyword">null</span>,&nbsp;&nbsp;<span class="comment">// شناسه نمودار فعال</span><br>
&nbsp;&nbsp;currentTool: <span class="string">'select'</span>,&nbsp;&nbsp;&nbsp;<span class="comment">// ابزار انتخاب‌شده</span><br>
&nbsp;&nbsp;selectedElement: <span class="keyword">null</span>,&nbsp;&nbsp;&nbsp;<span class="comment">// المان انتخاب‌شده</span><br>
&nbsp;&nbsp;isDragging: <span class="keyword">false</span>,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// وضعیت Drag</span><br>
&nbsp;&nbsp;isDrawingRelationship: <span class="keyword">false</span>,<br>
&nbsp;&nbsp;darkMode: <span class="keyword">true</span>,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// تم تاریک/روشن</span><br>
&nbsp;&nbsp;gridType: <span class="string">'lines'</span>,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// نوع خط‌کشی بوم</span><br>
&nbsp;&nbsp;fileMetadata: { ... }&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">// متادیتای پروژه</span><br>
};
</div>
</div>
</div>

<div class="section">
<div class="section-header">
<div class="section-number">۳</div>
<div class="section-title">
<h3>تکنولوژی‌های به‌کار رفته</h3>
<div class="subtitle">Technology Stack</div>
</div>
</div>
<div class="section-content">
<table class="styled-table">
<thead>
<tr>
<th>لایه</th>
<th>تکنولوژی</th>
<th>نقش در پروژه</th>
</tr>
</thead>
<tbody>
<tr>
<td>ساختار</td>
<td>HTML5 Semantic</td>
<td>ساختار صفحه و المان‌های DOM</td>
</tr>
<tr>
<td>استایل</td>
<td>CSS3 + CSS Variables</td>
<td>طراحی RTL، تم داینامیک، گرادیانت‌ها</td>
</tr>
<tr>
<td>رسم گرافیکی</td>
<td>HTML5 Canvas 2D API</td>
<td>موتور رسم نمودارها، Grid و المان‌ها</td>
</tr>
<tr>
<td>منطق برنامه</td>
<td>Vanilla JavaScript (ES6+)</td>
<td>State Management، Event Handling، Drawing Engine</td>
</tr>
<tr>
<td>ذخیره‌سازی</td>
<td>File API + JSON</td>
<td>Export/Import پروژه به‌صورت فایل JSON</td>
</tr>
<tr>
<td>فونت</td>
<td>Vazirmatn (Google Fonts)</td>
<td>تایپوگرافی فارسی حرفه‌ای</td>
</tr>
<tr>
<td>استایل کمکی</td>
<td>Tailwind CSS (CDN)</td>
<td>Utility classes برای UI سریع</td>
</tr>
</tbody>
</table>
<div class="info-box">
<strong>💡 نکته معماری:</strong> انتخاب Vanilla JS به جای فریم‌ورک‌هایی مانند React یا Angular، باعث کاهش چشمگیر حجم نهایی (Bundle Size)، اجرای سریع‌تر و عدم وابستگی به ابزارهای Build شده است. این رویکرد برای ابزارهای تخصصی و آفلاین، انتخاب بهینه‌ای است.
</div>
</div>
</div>

<div class="section">
<div class="section-header">
<div class="section-number">۴</div>
<div class="section-title">
<h3>نمودارهای پشتیبانی‌شده</h3>
<div class="subtitle">Supported UML Diagrams</div>
</div>
</div>
<div class="section-content">
<p>این ابزار هشت نوع نمودار استاندارد UML را پشتیبانی می‌کند که هر کدام در رجیستری <code>DIAGRAM_TYPES</code> تعریف شده‌اند. این رجیستری به‌عنوان <strong>Single Source of Truth</strong> برای ابزارها، گروه‌ها و قابلیت‌های هر نمودار عمل می‌کند.</p>
<div class="diagram-grid">
<div class="diagram-card">
<div class="icon">⭕</div>
<h4>Use Case Diagram</h4>
<p>مدل‌سازی Actorها، Use Caseها، System Boundary و روابط Include/Extend/Generalization.</p>
</div>
<div class="diagram-card">
<div class="icon">🔄</div>
<h4>State Machine Diagram</h4>
<p>Initial/Final State، Stateها، Composite State و Transition با برچسب اختیاری.</p>
</div>
<div class="diagram-card">
<div class="icon">🔀</div>
<h4>Activity Diagram</h4>
<p>Action، Decision/Merge، Fork/Join و جریان‌های Control/Object Flow.</p>
</div>
<div class="diagram-card">
<div class="icon">🧩</div>
<h4>Component Diagram</h4>
<p>Component، Interface (Provided/Required)، Package و روابط Dependency/Connector.</p>
</div>
<div class="diagram-card">
<div class="icon">🖥️</div>
<h4>Deployment Diagram</h4>
<p>Node، Device، Execution Environment، Artifact و روابط Deploy/Manifest.</p>
</div>
<div class="diagram-card">
<div class="icon">🗂️</div>
<h4>Class Diagram</h4>
<p>Class، Interface، Abstract، Enumeration با Attributes/Methods و روابط کامل UML.</p>
</div>
<div class="diagram-card">
<div class="icon">🔢</div>
<h4>Sequence Diagram</h4>
<p>Participant، Activation، Combined Fragment و پیام‌های Sync/Async/Return/Self.</p>
</div>
<div class="diagram-card">
<div class="icon">▤</div>
<h4>Swimlane Diagram</h4>
<p>Swimlane افقی/عمودی با قابلیت Resize، Action، Decision و Fork/Join درون آن.</p>
</div>
</div>
<div class="code-block">
<span class="comment">// Registry الگوی طراحی - نمونه تعریف یک نمودار</span><br>
<span class="keyword">const</span> DIAGRAM_TYPES = {<br>
&nbsp;&nbsp;usecase: {<br>
&nbsp;&nbsp;&nbsp;&nbsp;id: <span class="string">'usecase'</span>,<br>
&nbsp;&nbsp;&nbsp;&nbsp;name: <span class="string">'نمودار مورد کاربرد (Use Case)'</span>,<br>
&nbsp;&nbsp;&nbsp;&nbsp;icon: <span class="string">'⭕'</span>,<br>
&nbsp;&nbsp;&nbsp;&nbsp;tools: [<span class="string">'select'</span>, <span class="string">'actor'</span>, <span class="string">'useCase'</span>, <span class="string">'boundary'</span>, ...],<br>
&nbsp;&nbsp;&nbsp;&nbsp;groups: [<span class="string">'selection'</span>, <span class="string">'uc-elements'</span>, <span class="string">'uc-relations'</span>, ...]<br>
&nbsp;&nbsp;},<br>
&nbsp;&nbsp;<span class="comment">// ... سایر نمودارها</span><br>
};
</div>
</div>
</div>

<div class="section">
<div class="section-header">
<div class="section-number">۵</div>
<div class="section-title">
<h3>موتور رسم (Drawing Engine)</h3>
<div class="subtitle">Canvas Rendering Engine</div>
</div>
</div>
<div class="section-content">
<p>موتور رسم، قلب تپنده این پروژه است و بر پایه <strong>Immediate Mode Rendering</strong> طراحی شده است. در این رویکرد، در هر فریم (یا پس از هر تغییر state) کل بوم پاک‌سازی شده و تمامی المان‌ها از نو رسم می‌شوند. این رویکرد اگرچه در نگاه اول پرهزینه به نظر می‌رسد، اما به دلیل سادگی، قابلیت اطمینان بالا و عدم نیاز به مدیریت پیچیده Diff، برای این نوع ابزارها ایده‌آل است.</p>
<h4 style="margin-top: 20px; color: var(--primary); font-size: 16px;">چرخه رسم (Render Loop):</h4>
<ul class="module-list">
<li><strong>۱. پاک‌سازی بوم:</strong> <code>ctx.clearRect()</code> و رسم پس‌زمینه بر اساس تم.</li>
<li><strong>۲. رسم Grid:</strong> بر اساس نوع انتخابی (Lines/Dots/None) با استفاده از CSS Variables.</li>
<li><strong>۳. رسم المان‌ها:</strong> فراخوانی توابع اختصاصی هر نمودار (مانند <code>drawUseCase</code>, <code>drawClassDiagram</code>).</li>
<li><strong>۴. رسم روابط:</strong> پس از المان‌ها، روابط با محاسبه مرکز المان‌های مبدأ و مقصد رسم می‌شوند.</li>
<li><strong>۵. رسم Selection:</strong> المان انتخاب‌شده با خط‌چین آبی‌رنگ مشخص می‌شود.</li>
<li><strong>۶. رسم Relationship Preview:</strong> در حالت رسم رابطه، خط موقتی تا موقعیت ماوس نمایش داده می‌شود.</li>
</ul>
<div class="code-block">
<span class="comment">// چرخه اصلی رسم</span><br>
<span class="keyword">function</span> <span class="func">draw</span>() {<br>
&nbsp;&nbsp;<span class="keyword">const</span> d = <span class="func">getActive</span>(), tc = <span class="func">getTC</span>();<br>
&nbsp;&nbsp;ctx.<span class="func">clearRect</span>(0, 0, canvas.width, canvas.height);<br>
&nbsp;&nbsp;ctx.fillStyle = tc.canvasBg;<br>
&nbsp;&nbsp;ctx.<span class="func">fillRect</span>(0, 0, canvas.width, canvas.height);<br>
&nbsp;&nbsp;<span class="func">drawGrid</span>(ctx, canvas.width, canvas.height, tc, <span class="keyword">false</span>);<br>
&nbsp;&nbsp;<span class="keyword">if</span> (!d) <span class="keyword">return</span>;<br>
&nbsp;&nbsp;<span class="func">drawDiag</span>(ctx, d, tc);&nbsp;<span class="comment">// Dispatch به تابع اختصاصی</span><br>
}
</div>
<div class="info-box">
<strong>💡 بهینه‌سازی:</strong> برای جلوگیری از رسم‌های مکرر غیرضروری، از تکنیک <code>requestAnimationFrame</code> و <code>scheduleResize()</code> استفاده شده است تا Resize رویدادها به‌صورت هوشمند مدیریت شوند.
</div>
</div>
</div>

<div class="section">
<div class="section-header">
<div class="section-number">۶</div>
<div class="section-title">
<h3>سیستم مدیریت تم و رنگ</h3>
<div class="subtitle">Theme & Color System</div>
</div>
</div>
<div class="section-content">
<p>سیستم تم این پروژه بر پایه <strong>CSS Custom Properties (Variables)</strong> طراحی شده است. با تغییر کلاس <code>body.dark</code>، تمامی متغیرهای رنگی به‌صورت آنی تغییر می‌کنند و موتور رسم نیز از طریق تابع <code>getTC()</code> این مقادیر را در زمان اجرا (Runtime) از DOM استخراج می‌کند.</p>
<table class="styled-table">
<thead>
<tr>
<th>متغیر CSS</th>
<th>نقش</th>
<th>مثال مقدار (Dark Mode)</th>
</tr>
</thead>
<tbody>
<tr><td><code>--canvas-bg</code></td><td>پس‌زمینه بوم</td><td>#0f1724</td></tr>
<tr><td><code>--actor-stroke</code></td><td>رنگ خط Actor</td><td>#cbd5e0</td></tr>
<tr><td><code>--class-fill</code></td><td>رنگ زمینه Class</td><td>#1e2d40</td></tr>
<tr><td><code>--seq-msg</code></td><td>رنگ پیام‌های Sequence</td><td>#cbd5e0</td></tr>
<tr><td><code>--selected</code></td><td>رنگ المان انتخاب‌شده</td><td>#667eea</td></tr>
</tbody>
</table>
<div class="success-box">
<strong>✓ مزیت این رویکرد:</strong> افزودن یک تم جدید (مانند تم آبی، سبز یا High-Contrast) تنها با تغییر چند متغیر CSS امکان‌پذیر است و هیچ تغییری در منطق JavaScript نیاز نیست.
</div>
</div>
</div>

<div class="section">
<div class="section-header">
<div class="section-number">۷</div>
<div class="section-title">
<h3>سیستم تعامل و Hit Testing</h3>
<div class="subtitle">Interaction & Hit Testing System</div>
</div>
</div>
<div class="section-content">
<p>برای تشخیص اینکه ماوس روی کدام المان قرار دارد، از الگوی <strong>Hit Testing</strong> استفاده می‌شود. تابع <code>hitTest(x, y)</code> بر اساس نوع نمودار فعال، الگوریتم‌های متفاوتی را اجرا می‌کند:</p>
<ul class="module-list">
<li><strong>Actor:</strong> محاسبه فاصله اقلیتسی (Bounding Box 50×80 پیکسل).</li>
<li><strong>Use Case (بیضی):</strong> استفاده از معادله استاندارد بیضی <code>(x²/a²) + (y²/b²) ≤ 1</code>.</li>
<li><strong>State/Action/Component (مستطیل با گوشه گرد):</strong> بررسی Bounding Box.</li>
<li><strong>Decision (لوزی):</strong> استفاده از معادله لوزی <code>|x| + |y| ≤ size</code>.</li>
<li><strong>Class:</strong> بررسی بر اساس ارتفاع محاسبه‌شده داینامیک (<code>_computedH</code>).</li>
<li><strong>Swimlane:</strong> علاوه بر Hit Test بدنه، بررسی ۸ Handle برای Resize.</li>
</ul>
<div class="warning-box">
<strong>⚠️ چالش فنی:</strong> در نمودار Class، ارتفاع به‌صورت داینامیک بر اساس تعداد Attributes و Methods محاسبه می‌شود. این مقدار در هر Draw در <code>cls._computedH</code> ذخیره می‌گردد تا در Hit Test نیز قابل استفاده باشد.
</div>
</div>
</div>

<div class="section">
<div class="section-header">
<div class="section-number">۸</div>
<div class="section-title">
<h3>سیستم ذخیره‌سازی و بازیابی</h3>
<div class="subtitle">Persistence & File System</div>
</div>
</div>
<div class="section-content">
<p>سیستم Persistence این پروژه کاملاً مبتنی بر <strong>JSON File API</strong> است و از LocalStorage یا IndexedDB استفاده نمی‌کند. این تصمیم معماری، مزایای زیر را به همراه دارد:</p>
<ul class="module-list">
<li><strong>قابلیت اشتراک‌گذاری آسان:</strong> فایل JSON به‌راحتی قابل ارسال از طریق ایمیل، Git یا پیام‌رسان‌ها است.</li>
<li><strong>نسخه‌بندی (Versioning):</strong> فایل‌ها می‌توانند در Git نگهداری و Diff شوند.</li>
<li><strong>عدم وابستگی به مرورگر:</strong> با پاک‌سازی Cache مرورگر، داده‌ها از بین نمی‌روند.</li>
<li><strong>ساختار قابل توسعه:</strong> فرمت JSON به‌راحتی قابل گسترش است.</li>
</ul>
<div class="code-block">
<span class="comment">// ساختار فایل خروجی JSON</span><br>
{<br>
&nbsp;&nbsp;<span class="string">"version"</span>: <span class="string">"5.1"</span>,<br>
&nbsp;&nbsp;<span class="string">"fileTitle"</span>: <span class="string">"عنوان پروژه"</span>,<br>
&nbsp;&nbsp;<span class="string">"author"</span>: <span class="string">"هادی خزاعی اصل"</span>,<br>
&nbsp;&nbsp;<span class="string">"organization"</span>: <span class="string">"رایان پرداز کاوش"</span>,<br>
&nbsp;&nbsp;<span class="string">"createdAt"</span>: <span class="string">"2026-09-07T10:30:00.000Z"</span>,<br>
&nbsp;&nbsp;<span class="string">"preferences"</span>: { <span class="string">"gridType"</span>: <span class="string">"lines"</span>, <span class="string">"darkMode"</span>: <span class="keyword">true</span> },<br>
&nbsp;&nbsp;<span class="string">"diagrams"</span>: [ /* آرایه نمودارها */ ]<br>
}
</div>
<div class="info-box">
<strong>💡 Backward Compatibility:</strong> در هنگام Load فایل، کد با استفاده از <code>||</code> operator، فیلدهای اختیاری را مقداردهی پیش‌فرض می‌کند تا فایل‌های نسخه‌های قدیمی‌تر نیز بدون خطا بارگذاری شوند.
</div>
</div>
</div>

<div class="section">
<div class="section-header">
<div class="section-number">۹</div>
<div class="section-title">
<h3>الگوهای طراحی به‌کار رفته</h3>
<div class="subtitle">Design Patterns</div>
</div>
</div>
<div class="section-content">
<p>در این پروژه، چندین الگوی طراحی شناخته‌شده به‌صورت عملی پیاده‌سازی شده‌اند:</p>
<table class="styled-table">
<thead>
<tr>
<th>الگو</th>
<th>محل کاربرد</th>
<th>توضیح</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Registry Pattern</strong></td>
<td><code>DIAGRAM_TYPES</code></td>
<td>مرجع مرکزی تعریف انواع نمودار و قابلیت‌های آن‌ها</td>
</tr>
<tr>
<td><strong>State Pattern</strong></td>
<td>آبجکت <code>state</code></td>
<td>مدیریت وضعیت سراسری برنامه</td>
</tr>
<tr>
<td><strong>Strategy Pattern</strong></td>
<td>توابع <code>drawUseCase</code>, <code>drawClassDiagram</code>, ...</td>
<td>انتخاب استراتژی رسم بر اساس نوع نمودار</td>
</tr>
<tr>
<td><strong>Command Pattern</strong></td>
<td>مدیریت ابزارها (Tools)</td>
<td>هر ابزار یک رفتار مشخص را در mousedown/mouseup اجرا می‌کند</td>
</tr>
<tr>
<td><strong>Observer Pattern</strong></td>
<td>Event Listeners</td>
<td>واکنش به رویدادهای Mouse، Keyboard و UI</td>
</tr>
<tr>
<td><strong>Factory Pattern</strong></td>
<td><code>createNewDiagram()</code></td>
<td>ساخت ساختار اولیه نمودار بر اساس نوع</td>
</tr>
<tr>
<td><strong>Singleton Pattern</strong></td>
<td>Canvas Context، State Object</td>
<td>تنها یک نمونه از منابع حیاتی در طول اجرا</td>
</tr>
</tbody>
</table>
</div>
</div>

<div class="section">
<div class="section-header">
<div class="section-number">۱۰</div>
<div class="section-title">
<h3>قابلیت‌های کلیدی و UX</h3>
<div class="subtitle">Key Features & UX</div>
</div>
</div>
<div class="section-content">
<ul class="module-list">
<li><strong>🗂️ مدیریت چند نمودار (Multi-Tab):</strong> امکان باز کردن هم‌زمان چندین نمودار در تب‌های مجزا با امکان سوئیچ سریع.</li>
<li><strong>🌙 تم تاریک/روشن:</strong> سوئیچ آنی با حفظ ترجیحات در فایل پروژه.</li>
<li><strong>⌨️ کلیدهای میانبر:</strong> Ctrl+S (ذخیره)، Ctrl+N (جدید)، Delete (حذف)، B (Sidebar)، G (Grid).</li>
<li><strong>🎨 Properties Panel:</strong> ویرایش زنده ویژگی‌های المان انتخاب‌شده در پایین صفحه.</li>
<li><strong>🗂️ Class Editor Modal:</strong> ویرایشگر پیشرفته Attributes و Methods با Visibility، Static و Type.</li>
<li><strong>📐 Resize Handles:</strong> قابلیت تغییر اندازه Swimlane با ۸ Handle در گوشه‌ها و اضلاع.</li>
<li><strong>🖼️ Export to PNG:</strong> خروجی با کیفیت از نمودار فعلی با قابلیت انتخاب رنگ پس‌زمینه یا شفافیت.</li>
<li><strong>📦 Grid System:</strong> سه حالت (Lines, Dots, None) برای کمک به چیدمان دقیق المان‌ها.</li>
<li><strong>🔗 Relationship Drawing:</strong> رسم روابط با Drag از مبدأ به مقصد و Prompt برای برچسب و Multiplicity.</li>
</ul>
</div>
</div>

<div class="section">
<div class="section-header">
<div class="section-number">۱۱</div>
<div class="section-title">
<h3>نقاط قوت و پیشنهادات بهبود</h3>
<div class="subtitle">Strengths & Recommendations</div>
</div>
</div>
<div class="section-content">
<table class="styled-table">
<thead>
<tr>
<th>دسته</th>
<th>نقاط قوت فعلی</th>
<th>پیشنهادات بهبود</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>معماری</strong></td>
<td>بدون وابستگی خارجی، سبک و سریع</td>
<td>ماژولار کردن کد با ES Modules برای نگهداری بهتر</td>
</tr>
<tr>
<td><strong>عملکرد</strong></td>
<td>اجرای روان در نمودارهای کوچک و متوسط</td>
<td>پیاده‌سازی Dirty Rectangle Rendering برای نمودارهای بزرگ</td>
</tr>
<tr>
<td><strong>UX</strong></td>
<td>رابط کاربری شهودی با RTL کامل</td>
<td>افزودن Undo/Redo با Command History</td>
</tr>
<tr>
<td><strong>ذخیره‌سازی</strong></td>
<td>خروجی JSON قابل اشتراک</td>
<td>افزودن Auto-Save در LocalStorage به‌عنوان Backup</td>
</tr>
<tr>
<td><strong>توسعه‌پذیری</strong></td>
<td>Registry Pattern برای افزودن نمودار جدید</td>
<td>افزودن نمودارهای Package و Communication</td>
</tr>
<tr>
<td><strong>خروجی</strong></td>
<td>PNG با کیفیت</td>
<td>افزودن Export به SVG، PDF و XMI استاندارد</td>
</tr>
</tbody>
</table>
</div>
</div>

<div class="section">
<div class="section-header">
<div class="section-number">۱۲</div>
<div class="section-title">
<h3>جمع‌بندی نهایی</h3>
<div class="subtitle">Final Conclusion</div>
</div>
</div>
<div class="section-content">
<p>پروژه «ابزار رسم نمودارهای UML» یک نمونه موفق از توسعه یک اپلیکیشن تخصصی و حرفه‌ای با استفاده از تکنولوژی‌های پایه وب است. این ابزار با تکیه بر معماری State-Centric، موتور رسم مبتنی بر Canvas و سیستم Registry منعطف، توانایی مدل‌سازی کامل سیستم‌های نرم‌افزاری را در محیطی آفلاین و سبک فراهم می‌آورد.</p>
<div class="conclusion-box">
<h3>🎯 نتیجه‌گیری معماری</h3>
<p>این پروژه نشان می‌دهد که برای ساخت ابزارهای تخصصی، لزومی به استفاده از فریم‌ورک‌های سنگین نیست. با طراحی دقیق معماری، انتخاب الگوهای مناسب و تمرکز بر تجربه کاربری، می‌توان محصولی در سطح Enterprise تولید کرد که کاملاً مستقل، قابل نگهداری و توسعه‌پذیر باشد.</p>
<div class="highlight">
<strong>💎 ارزش نهایی:</strong> ابزاری کاملاً آفلاین، سبک، حرفه‌ای و مستقل که نیاز هر معمار و تحلیل‌گر نرم‌افزار را برای مدل‌سازی سریع و دقیق برطرف می‌سازد.
</div>
</div>
<div class="info-box">
<strong>📞 گام بعدی:</strong> برای توسعه نسخه‌های آتی، پیشنهاد می‌شود ماژول‌های Undo/Redo، Export به فرمت‌های استاندارد (XMI, SVG) و افزودن نمودارهای Interaction اضافه گردد.
</div>
</div>
</div>

<div class="page-info">
این مستند محرمانه بوده و صرفاً جهت استفاده داخلی تیم فنی تهیه شده است. هرگونه کپی‌برداری یا افشا بدون اجازه کتبی ممنوع است.
</div>
</div>
<footer class="document-footer">
<div class="footer-brand">طراحی، تحلیل و توسعه: هادی خزاعی اصل</div>
<div class="footer-divider"></div>
<div>تاریخ تهیه: ۱۶ شهریور ۱۴۰۵ — ساعت ۱۰:۳۰ &nbsp;|&nbsp; نسخه: ۱.۰ &nbsp;|&nbsp; طبقه‌بندی: محرمانه / فنی</div>
</footer>
</div>
</body>
</html>

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Resources\XResourceIDS\ExceptionResourceIDS.ts

export const AvailableTranslationResources: XLanguageDescriptor[] = [
  {
    name: 'persian',
    locale: 'fa-IR',
    language: 'fa',
    direction: 'rtl',
    resources: [
      //
      //#region Exception Resource IDS ...
      {
        id: 'ex_100',
        value: 'داده نا معتبر',
      },
      {
        id: 'ex_101',
        value: 'تاریخ نامعتبر',
      },
      {
        id: 'ex_102',
        value: 'درخواست اشتباه',
      },
      {
        id: 'ex_103',
        value: 'ورودی نامعتبر',
      },
      {
        id: 'ex_104',
        value: 'پیام نامعتبر',
      },
      {
        id: 'ex_105',
        value: 'نشانه نامعتبر',
      },
      {
        id: 'ex_106',
        value: 'کد اععتبارسنجی نامعتبر',
      },
      {
        id: 'ex_107',
        value: 'پست الکترونیک نامعتبر',
      },
      {
        id: 'ex_108',
        value: 'شماره تلفن همراه نامعتبر',
      },
      {
        id: 'ex_109',
        value: 'کلمه عبور نامعتبر',
      },
      {
        id: 'ex_110',
        value: 'آدرس اینترنتی نامعتبر',
      },
      {
        id: 'ex_111',
        value: 'دستگاه نامعتبر',
      },
      {
        id: 'ex_112',
        value: 'تصویر پروفایل نامعتبر',
      },
      {
        id: 'ex_113',
        value: 'فایل نامعتبر',
      },
      {
        id: 'ex_114',
        value: 'نام کاربری نامعتبر',
      },
      {
        id: 'ex_200',
        value: 'یافت نشد',
      },
      {
        id: 'ex_201',
        value: 'عدم تایید جواز',
      },
      {
        id: 'ex_202',
        value: 'سطح دسترسی نامعتبر',
      },
      {
        id: 'ex_203',
        value: 'غیر قابل نمایش',
      },
      {
        id: 'ex_300',
        value: 'حساب کاربری قفل شده است',
      },
      {
        id: 'ex_301',
        value: 'خطا در ورود',
      },
      {
        id: 'ex_302',
        value: 'کاربر قبلا ثبت نام کرده است',
      },
      {
        id: 'ex_303',
        value: 'کاربر غیرفعال است',
      },
      {
        id: 'ex_304',
        value: 'دستگاه شما قفل شده است تا : (0)',
      },
      {
        id: 'ex_305',
        value: 'امکان درخواست کد اعتبارسنجی وجود ندارد تا : (0)',
      },
      {
        id: 'ex_306',
        value: 'کلمه عبورها یکسان هستند',
      },
      {
        id: 'ex_307',
        value: 'شماره های تلفن یکسان هستند',
      },
      {
        id: 'ex_308',
        value: 'آدرس های پست الکترونیک یکسان هستند',
      },
      {
        id: 'ex_309',
        value: 'شماره تلفن قبلا ثبت شده است',
      },
      {
        id: 'ex_310',
        value: 'پست الکترونیک قبلا ثبت شده است',
      },
      {
        id: 'ex_311',
        value: 'تلفن همراه تایید نشده است',
      },
      {
        id: 'ex_312',
        value: 'پست الکترونیک تایید نشده است',
      },
      {
        id: 'ex_313',
        value: 'مطلب ممنوعه',
      },
      {
        id: 'ex_314',
        value: 'کاربر ممنوعه',
      },
      {
        id: 'ex_400',
        value: 'خطای نا شناخته',
      },
      {
        id: 'ex_401',
        value: 'مورد تکراری',
      },
      {
        id: 'ex_402',
        value: 'شکست عملیات',
      },
      {
        id: 'ex_403',
        value: 'تاریخ مصرف منقضی شده است',
      },
      {
        id: 'ex_404',
        value: 'غیر قابل اسفاده',
      },
      {
        id: 'ex_405',
        value: 'تعداد برچسب ها بیش از جد مجاز است',
      },
      {
        id: 'ex_406',
        value: 'اتمام مهلت زمانی',
      },
      {
        id: 'ex_500',
        value: 'پرونده خالی است',
      },
      {
        id: 'ex_501',
        value: 'اندازه بیش از حد مجاز',
      },
      {
        id: 'ex_502',
        value: 'گونه نامعتبر فایل',
      },
      {
        id: 'ex_503',
        value: 'تعداد رسانه ها بیش از حد مجاز است',
      },
      {
        id: 'ex_600',
        value: 'پیکربندی اشتباه',
      },
      {
        id: 'ex_601',
        value: 'خطا در پیکربندی سامانه ذخیره سازی',
      },
      {
        id: 'ex_602',
        value: 'خطا در پیکربندی سامانه ذخیره سازی',
      },
      {
        id: 'ex_900',
        value: 'تعداد فایل ها بیش از حد مجاز است',
      },
      {
        id: 'ex_901',
        value: 'مجموع اندازه فایل ها کمتر از حد مجاز است',
      },
      {
        id: 'ex_902',
        value: 'مجموع اندازه فایل ها بیش از حد مجاز است',
      },
      {
        id: 'ex_903',
        value: 'شرایط و ضوابط باید پذیرفته شوند',
      },
      {
        id: 'ex_904',
        value: 'عدم پشتیبانی از سیستم موقعیت یاب',
      },
      {
        id: 'ex_905',
        value: 'مجوز دسترسی به موقعیت خود را صادر نکرده اید',
      },
      {
        id: 'ex_906',
        value: 'دسترسی به موقعیت جغرافیایی امکان پذیر نیست',
      },
      {
        id: 'ex_907',
        value: 'عدم پشتیبانی از سیستم ذخیره سازی',
      },
      {
        id: 'ex_908',
        value: 'برای دسترسی به محتوا باید از سامانه خارج شوید',
      },
      {
        id: 'ex_909',
        value: 'زبان انتخاب شده نامعتبر است',
      },
      {
        id: 'ex_910',
        value: 'داده نامعتبر برای انتخاب کاربر',
      },
      //#endregion
    ],
  },
  {
    name: 'english',
    locale: 'en-US',
    language: 'en',
    direction: 'ltr',
    resources: [
      //
      //#region Exception Resource IDS ...
      {
        id: 'ex_100',
        value: 'Invalid Data ...',
      },
      {
        id: 'ex_101',
        value: 'Invalid Date ...',
      },
      {
        id: 'ex_102',
        value: 'Bad Request ...',
      },
      {
        id: 'ex_103',
        value: 'Invalid Args ...',
      },
      {
        id: 'ex_104',
        value: 'Invalid Message ...',
      },
      {
        id: 'ex_105',
        value: 'Invalid Token ...',
      },
      {
        id: 'ex_106',
        value: 'Invalid Verification Code ...',
      },
      {
        id: 'ex_107',
        value: 'Invalid Email Address ...',
      },
      {
        id: 'ex_108',
        value: 'Invalid Mobile Number ...',
      },
      {
        id: 'ex_109',
        value: 'Invalid Password ...',
      },
      {
        id: 'ex_110',
        value: 'Invalid URL ...',
      },
      {
        id: 'ex_111',
        value: 'Invalid Device ...',
      },
      {
        id: 'ex_112',
        value: 'Invalid Profile Image ...',
      },
      {
        id: 'ex_113',
        value: 'Invalid File ...',
      },
      {
        id: 'ex_114',
        value: 'Invalid Username ...',
      },
      {
        id: 'ex_200',
        value: 'Not Found ...',
      },
      {
        id: 'ex_201',
        value: 'Not Allowed ...',
      },
      {
        id: 'ex_202',
        value: 'Not Authorized ...',
      },
      {
        id: 'ex_203',
        value: 'Not Visible ...',
      },
      {
        id: 'ex_300',
        value: 'Account Locked out ...',
      },
      {
        id: 'ex_301',
        value: 'Login Failed ...',
      },
      {
        id: 'ex_302',
        value: 'User Registered Before ...',
      },
      {
        id: 'ex_303',
        value: 'User Disabled ...',
      },
      {
        id: 'ex_304',
        value: 'Device Banned Until:(0)',
      },
      {
        id: 'ex_305',
        value: `You can not Request Verification code Until:(0)`,
      },
      {
        id: 'ex_306',
        value: 'Passwords Same ...',
      },
      {
        id: 'ex_307',
        value: 'Mobiles Same ...',
      },
      {
        id: 'ex_308',
        value: 'Emails Same ...',
      },
      {
        id: 'ex_309',
        value: 'Mobile In Used ...',
      },
      {
        id: 'ex_310',
        value: 'Email In Used ...',
      },
      {
        id: 'ex_311',
        value: 'Mobile Not Confirmed ...',
      },
      {
        id: 'ex_312',
        value: 'Email Not Confirmed ...',
      },
      {
        id: 'ex_313',
        value: 'Post Banned ...',
      },
      {
        id: 'ex_314',
        value: 'User Banned ...',
      },
      {
        id: 'ex_400',
        value: 'Unknown Error ...',
      },
      {
        id: 'ex_401',
        value: 'Duplicate ...',
      },
      {
        id: 'ex_402',
        value: 'Action Failed ...',
      },
      {
        id: 'ex_403',
        value: 'Expired Date ...',
      },
      {
        id: 'ex_404',
        value: 'Unavailable ...',
      },
      {
        id: 'ex_405',
        value: 'Max Allowed Tag Exceeded ...',
      },
      {
        id: 'ex_406',
        value: 'Timeout ...',
      },
      {
        id: 'ex_500',
        value: 'Empty File ...',
      },
      {
        id: 'ex_501',
        value: 'max File size exceeded ...',
      },
      {
        id: 'ex_502',
        value: 'Unsupported File Type ...',
      },
      {
        id: 'ex_503',
        value: 'Max Allowed Media Exceeded ...',
      },
      {
        id: 'ex_600',
        value: 'Invalid Configuration ...',
      },
      {
        id: 'ex_601',
        value: 'Storage Service Initiallization Failed ...',
      },
      {
        id: 'ex_602',
        value: 'Message Service Initialization Failed ...',
      },
      {
        id: 'ex_900',
        value: 'Max Allowed Files Exceeded ...',
      },
      {
        id: 'ex_901',
        value: 'Min Files Size Exceeded ...',
      },
      {
        id: 'ex_902',
        value: 'Max Files Size Exceeded ...',
      },
      {
        id: 'ex_903',
        value: 'Must Accept Terms And Conditions ...',
      },
      {
        id: 'ex_904',
        value: 'Browser does not support Location Services ...',
      },
      {
        id: 'ex_905',
        value: 'You have rejected access to your Location ...',
      },
      {
        id: 'ex_906',
        value: 'Unable to determine your Location ...',
      },
      {
        id: 'ex_907',
        value: 'Browser does not support Storage Services ...',
      },
      {
        id: 'ex_908',
        value: 'Must Logout to access content ...',
      },
      {
        id: 'ex_909',
        value: 'Invalid Locale ...',
      },
      {
        id: 'ex_910',
        value: 'Invalid User Selector ...',
      },
      //#endregion
    ],
  },
];

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Resources\XResourceIDS\XResourceIDS.html

<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>مستندات منابع ترجمه (AvailableTranslationResources)</title>
    <style>
        body {
            font-family: 'Tahoma', 'Segoe UI', Arial, sans-serif;
            background-color: #f9f9f9;
            color: #333;
            margin: 0;
            padding: 20px;
            line-height: 1.6;
        }
        .container {
            max-width: 1200px;
            margin: 0 auto;
            background: #fff;
            padding: 25px;
            border-radius: 8px;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
        }
        .header {
            border-bottom: 2px solid #007bff;
            padding-bottom: 15px;
            margin-bottom: 20px;
        }
        .header h1 {
            margin: 0;
            color: #0056b3;
            font-size: 24px;
        }
        .meta-info {
            margin-top: 10px;
            font-size: 14px;
            color: #666;
        }
        .meta-info span {
            margin-left: 20px;
            font-weight: bold;
        }
        pre {
            background-color: #2d2d2d;
            color: #f8f8f2;
            padding: 20px;
            border-radius: 6px;
            overflow-x: auto;
            direction: ltr;
            text-align: left;
            font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
            font-size: 14px;
        }
        .footer {
            margin-top: 20px;
            text-align: center;
            font-size: 12px;
            color: #888;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>مستندات به‌روزرسانی منابع ترجمه</h1>
            <div class="meta-info">
                <span>توسعه‌دهنده: هادی خزاعی اصل</span>
                <span>شرکت: فن آوران ساحر علم</span>
                <span>تاریخ: ۱۴۰۵/۰۵/۳۱</span>
            </div>
        </div>

        <p>کد به‌روزرسانی‌شده `AvailableTranslationResources` شامل کلیدهای جدید درخواست‌شده در زیر ارائه شده است:</p>

        <pre><code>export const AvailableTranslationResources: XLanguageDescriptor[] = [
  {
    name: 'persian',
    locale: 'fa-IR',
    language: 'fa',
    direction: 'rtl',
    resources: [
      { id: 'app_name', value: 'ساحر علم' },
      { id: 'company', value: 'فن آوران ساحر علم' },
      { id: 'id', value: 'شناسه' },
      { id: 'file_name', value: 'نام فایل' },
      { id: 'error', value: 'خطا' },
      { id: 'required', value: 'الزامی است.' },
      { id: 'loading', value: 'بارگزاری' },
      { id: 'username', value: 'نام کاربری' },
      { id: 'password', value: 'کلمه عبور' },
      { id: 'first_name', value: 'نام' },
      { id: 'last_name', value: 'نام خانوادگی' },
      { id: 'verified', value: 'تایید شده' },
      { id: 'unverified', value: 'تایید نشده' },
      { id: 'thumbnail', value: 'پیش نمایش' },
      { id: 'language', value: 'زبان' },
      { id: 'take_screenshot', value: 'تصویر برداری ازمحیط' },
      { id: 'enable_capture_mode', value: 'فعالسازی حالت تصویر برداری' },
      { id: 'disable_capture_mode', value: 'غیرفعالسازی حالت تصویر برداری' },
      { id: 'be', value: 'باشد' },
      { id: 'not_to_be', value: 'نباشد' },
      { id: 'enabled', value: 'فعال' },
      { id: 'disabled', value: 'غیر فعال' },
      { id: 'banned', value: 'مسدود' },
      { id: 'un_banned', value: 'غیر مسدود' },
      { id: 'audio', value: 'صوت' },
      { id: 'video', value: 'فیلم' },
      { id: 'cover_image', value: 'تصویر' },
      { id: 'image', value: 'تصویر' },
      { id: 'unknown', value: 'ناشناس' },
      { id: 'location', value: 'موقعیت جغرافیای' },
      { id: 'are_you_sure', value: 'اطمینان دارید ؟' },
      { id: 'success_msg', value: 'عملیات با موفقیت انجام شد' },
      { id: 'locked_msg', value: 'برای ترک این صفحه باید فعالیت جاری را لغو کرده و یا به اتمام برسانید' },
      { id: 'empty_content', value: 'خالی است' },
      { id: 'no_item', value: 'محتوایی ثبت نشده است' },
      { id: 'drag_drop_here', value: 'محل رها کردن فایل' },
      { id: 'map_dialog_title', value: 'نمایش موقعیت مکانی' },
      { id: 'map_dialog_select_title', value: 'انتخاب موقعیت جغرافیایی' },
      { id: 'color_picker_dialog_title', value: 'انتخاب رنگ' },
      { id: 'dismiss', value: 'رد کردن' },
      { id: 'accept', value: 'می پذیرم' },
      { id: 'ok', value: 'بسیار خب' },
      { id: 'save', value: 'ذخیره' },
      { id: 'cancel', value: 'لغو' },
      { id: 'retry', value: 'سعی مجدد' },
      { id: 'edit', value: 'ویرایش' },
      { id: 'reset', value: 'باز نشاندن' },
      { id: 'change', value: 'تغییر' },
      { id: 'start', value: 'آغاز' },
      { id: 'yes', value: 'بله' },
      { id: 'no', value: 'خیر' },
      { id: 'verify', value: 'تایید کردن' },
      { id: 'confirm', value: 'تاییدیه' },
      { id: 'crop', value: 'برش' },
      { id: 'clear', value: 'پاک کردن' },
      { id: 'set', value: 'تنظیم' },
      { id: 'select', value: 'انتخاب' },
      { id: 'send', value: 'ارسال' },
      { id: 'show', value: 'نمایش دادن' },
      { id: 'hide', value: 'پنهان کردن' },
      { id: 'search', value: 'جستجو' },
      { id: 'delete', value: 'حذف' },
      { id: 'clone', value: 'نمونه گیری' },
      { id: 'select_all', value: 'انتخاب همه' },
      { id: 'select_file', value: 'انتخاب فایل(ها)' },
      { id: 'select_all_group', value: 'انتخاب همه موارد فیلتر شده' },
      { id: 'deselect_all', value: 'لغو همه انتخاب ها' },
      { id: 'deselect_all_group', value: 'لغو انتخاب همه موارد فیلتر شده' },
      { id: 'inverse_selection', value: 'انتخاب معکوس' },
      { id: 'selected_location', value: 'انتخاب محل' },
      { id: 'preset_colors', value: 'رنگ های پیش فرض' },
      { id: 'copy_to_clipboard', value: 'ثبت در حافظه موقت' },
      { id: 'color', value: 'رنگ' },
      { id: 'colors', value: 'رنگها' },
      { id: 'upload', value: 'بارگزاری' },
      { id: 'import', value: 'وارد کردن' },
      { id: 'continue', value: 'ادامه' },
      { id: 'finish', value: 'پایان' },
      { id: 'enable', value: 'فعال' },
      { id: 'disable', value: 'غیر فعال' },
      { id: 'all', value: 'همه' },
      { id: 'new', value: 'جدید' },
      { id: 'add', value: 'افزودن' },
      { id: 'update', value: 'بروز رسانی' },
      { id: 'details', value: 'جزئیات' },
      { id: 'translation', value: 'ترجمه' },
      { id: 'download', value: 'دریافت' },
      { id: 'list', value: 'فهرست' },
      { id: 'options', value: 'انتخاب ها' },
      { id: 'descriptions', value: 'توضیحات' },
      { id: 'type', value: 'نوع' },
      { id: 'media', value: 'رسانه' },
      { id: 'medias', value: 'رسانه ها' },
      { id: 'summary', value: 'خلاصه' },
      { id: 'go', value: 'رفتن' },
      { id: 'input', value: 'ورود' },
      { id: 'preview', value: 'پیش نمایش' },
      { id: 'close', value: 'بستن' },
      { id: 'undo', value: 'لغو کردن' },
      { id: 'redo', value: 'انجام دوباره' },
      { id: 'refresh', value: 'بازیابی' },
      { id: 'submit', value: 'ثبت' },
      { id: 'first', value: 'نخستین' },
      { id: 'last', value: 'آخرین' },
      { id: 'previous', value: 'قبلی' },
      { id: 'next', value: 'بعدی' },
      { id: 'back', value: 'بازگشت' },
      { id: 'x_markdown_preview', value: 'پیش نمایش' },
      { id: 'x_markdown_image', value: 'تصویر' },
      { id: 'x_markdown_link', value: 'پیوند' },
      { id: 'x_markdown_ol', value: 'فهرست ترتیبی' },
      { id: 'x_markdown_ul', value: 'فهرست بدون ترتیب' },
      { id: 'x_markdown_quote', value: 'نقل قول' },
      { id: 'x_markdown_code', value: 'کد منبع' },
      { id: 'x_markdown_heading', value: 'عنوان' },
      { id: 'x_markdown_italic', value: 'ایتالیک' },
      { id: 'x_markdown_bold', value: 'پر رنگ' },
      { id: 'x_markdown_guide', value: 'راهنمای نگارش' },
      { id: 'theme', value: 'پوسته' },
      { id: 'theme_mode', value: 'حالت نمایشی' },
      { id: 'theme_dark', value: 'تیره' },
      { id: 'theme_light', value: 'روشن' },
      { id: 'theme_name', value: 'نام پوسته' },
      { id: 'theme_name_description', value: 'نام پوسته را وارد کنید' },
      { id: 'theme_clone_title', value: 'نمونه گیری از پوسته' },
      { id: 'input_color_title', value: 'تعیین رنگ' },
      { id: 'input_color_description', value: 'مقدار رنگ را وارد کنید' },
      { id: 'ide_mode', value: 'نوع کد' },
      { id: 'ide_format', value: 'حالت دهی کد' },
      // ... (سایر آیتم‌های موجود در لیست اصلی حفظ شده‌اند) ...
      
      // --- موارد جدید اضافه شده ---
      { id: 'print', value: 'چاپ' },
      { id: 'export_png', value: 'خروجی PNG' },
      { id: 'export_svg', value: 'خروجی SVG' },
      { id: 'export_csv', value: 'خروجی CSV' },
      { id: 'zoom_in', value: 'بزرگنمایی' },
      { id: 'zoom_out', value: 'کوچک نمایی' },
      { id: 'zoom_selection', value: 'بزرگنمایی ناحیه' },
      { id: 'x_axis', value: 'محور افقی' },
      { id: 'y_axis', value: 'محور عمودی' },
      { id: 'xy_axis', value: 'محورهای عمودی و افقی' },
      { id: 'pan', value: 'حرکت آزاد' },
      { id: 'full_screen', value: 'حالت تمام صفحه' }
    ],
  },
  {
    name: 'english',
    locale: 'en-US',
    language: 'en',
    direction: 'ltr',
    resources: [
      { id: 'app_name', value: 'SaherElm' },
      { id: 'company', value: 'SaherElm IT Center' },
      { id: 'id', value: 'ID' },
      { id: 'file_name', value: 'File Name' },
      { id: 'error', value: 'Error' },
      { id: 'required', value: 'required' },
      { id: 'loading', value: 'loading' },
      { id: 'username', value: 'Username' },
      { id: 'password', value: 'Password' },
      { id: 'first_name', value: 'Firstname' },
      { id: 'last_name', value: 'Lastname' },
      { id: 'verified', value: 'Verified' },
      { id: 'unverified', value: 'Unverified' },
      { id: 'thumbnail', value: 'Thumbnail' },
      { id: 'language', value: 'Language' },
      { id: 'take_screenshot', value: 'Take Screenshot' },
      { id: 'enable_capture_mode', value: 'Enable Capture Mode' },
      { id: 'disable_capture_mode', value: 'Disable Capture Mode' },
      { id: 'be', value: 'Be' },
      { id: 'not_to_be', value: 'Not to be' },
      { id: 'enabled', value: 'Enabled' },
      { id: 'disabled', value: 'Disabled' },
      { id: 'banned', value: 'Banned' },
      { id: 'un_banned', value: 'UnBanned' },
      { id: 'audio', value: 'Audio' },
      { id: 'video', value: 'Video' },
      { id: 'cover_image', value: 'Cover Image' },
      { id: 'image', value: 'Image' },
      { id: 'avatar', value: 'Avatar' },
      { id: 'unknown', value: 'Unknown' },
      { id: 'location', value: 'Location' },
      { id: 'are_you_sure', value: 'Are You Sure ?' },
      { id: 'success_msg', value: 'Operation done Successfully ...' },
      { id: 'locked_msg', value: 'You have to finish/cancel current proccess for leaving' },
      { id: 'empty_content', value: 'Empty' },
      { id: 'no_item', value: 'there is no Content' },
      { id: 'drag_drop_here', value: 'Drop File Here' },
      { id: 'map_dialog_title', value: 'Show Location' },
      { id: 'map_dialog_select_title', value: 'Select Location' },
      { id: 'color_picker_dialog_title', value: 'Select Color' },
      { id: 'dismiss', value: 'dismiss' },
      { id: 'accept', value: 'Accept' },
      { id: 'ok', value: 'Ok' },
      { id: 'save', value: 'Save' },
      { id: 'cancel', value: 'Cancel' },
      { id: 'retry', value: 'Retry' },
      { id: 'edit', value: 'Edit' },
      { id: 'reset', value: 'Reset' },
      { id: 'change', value: 'Change' },
      { id: 'start', value: 'Start' },
      { id: 'yes', value: 'Yes' },
      { id: 'no', value: 'No' },
      { id: 'verify', value: 'Verify' },
      { id: 'confirm', value: 'Confirm' },
      { id: 'crop', value: 'Crop' },
      { id: 'clear', value: 'Clear' },
      { id: 'set', value: 'Set' },
      { id: 'select', value: 'Select' },
      { id: 'send', value: 'Send' },
      { id: 'show', value: 'Show' },
      { id: 'hide', value: 'Hide' },
      { id: 'search', value: 'Search' },
      { id: 'delete', value: 'Delete' },
      { id: 'clone', value: 'Clone' },
      { id: 'select_all', value: 'Select All' },
      { id: 'select_file', value: 'Select File(s)' },
      { id: 'select_all_group', value: 'Select All Filtered Items' },
      { id: 'deselect_all', value: 'Deselect All' },
      { id: 'deselect_all_group', value: 'Deselect All Filtered Items' },
      { id: 'inverse_selection', value: 'Inverse Selection' },
      { id: 'selected_location', value: 'Selected Location' },
      { id: 'preset_colors', value: 'Preset Colors' },
      { id: 'copy_to_clipboard', value: 'Copy to Clipboard' },
      { id: 'color', value: 'Color' },
      { id: 'colors', value: 'Colors' },
      { id: 'upload', value: 'Upload' },
      { id: 'import', value: 'Import' },
      { id: 'continue', value: 'Continue' },
      { id: 'finish', value: 'Finish' },
      { id: 'enable', value: 'Enable' },
      { id: 'disable', value: 'Disable' },
      { id: 'all', value: 'All' },
      { id: 'new', value: 'New' },
      { id: 'add', value: 'Add' },
      { id: 'update', value: 'Update' },
      { id: 'details', value: 'Details' },
      { id: 'translation', value: 'Translation' },
      { id: 'download', value: 'Download' },
      { id: 'list', value: 'List' },
      { id: 'options', value: 'Options' },
      { id: 'descriptions', value: 'Description' },
      { id: 'type', value: 'Type' },
      { id: 'media', value: 'Media' },
      { id: 'medias', value: 'Medias' },
      { id: 'summary', value: 'Summary' },
      { id: 'go', value: 'Go' },
      { id: 'input', value: 'Input' },
      { id: 'preview', value: 'Preview' },
      { id: 'close', value: 'Close' },
      { id: 'undo', value: 'Undo' },
      { id: 'redo', value: 'Redo' },
      { id: 'refresh', value: 'Refresh' },
      { id: 'submit', value: 'Submit' },
      { id: 'first', value: 'First' },
      { id: 'last', value: 'Last' },
      { id: 'previous', value: 'Previous' },
      { id: 'next', value: 'Next' },
      { id: 'back', value: 'Back' },
      { id: 'x_markdown_preview', value: 'Preview' },
      { id: 'x_markdown_image', value: 'Image' },
      { id: 'x_markdown_link', value: 'Link' },
      { id: 'x_markdown_ol', value: 'Ordered List' },
      { id: 'x_markdown_ul', value: 'Unordered List' },
      { id: 'x_markdown_quote', value: 'Quote' },
      { id: 'x_markdown_code', value: 'Code' },
      { id: 'x_markdown_heading', value: 'Heading' },
      { id: 'x_markdown_italic', value: 'Italic' },
      { id: 'x_markdown_bold', value: 'Bold' },
      { id: 'x_markdown_guide', value: 'Markdown Guide' },
      { id: 'theme', value: 'Theme' },
      { id: 'theme_mode', value: 'Theme Mode' },
      { id: 'theme_dark', value: 'Dark' },
      { id: 'theme_light', value: 'Light' },
      { id: 'theme_name', value: 'Theme Name' },
      { id: 'theme_name_description', value: 'Enter Theme name' },
      { id: 'theme_clone_title', value: 'Theme Cloning' },
      { id: 'input_color_title', value: 'Color Value' },
      { id: 'input_color_description', value: 'input color value' },
      { id: 'ide_mode', value: 'Code Style' },
      { id: 'ide_format', value: 'Code Formatting' },
      // ... (سایر آیتم‌های موجود در لیست اصلی حفظ شده‌اند) ...

      // --- موارد جدید اضافه شده ---
      { id: 'print', value: 'Print' },
      { id: 'export_png', value: 'Export PNG' },
      { id: 'export_svg', value: 'Export SVG' },
      { id: 'export_csv', value: 'Export CSV' },
      { id: 'zoom_in', value: 'Zoom In' },
      { id: 'zoom_out', value: 'Zoom Out' },
      { id: 'zoom_selection', value: 'Zoom Selection' },
      { id: 'x_axis', value: 'X Axis' },
      { id: 'y_axis', value: 'Y Axis' },
      { id: 'xy_axis', value: 'XY Axis' },
      { id: 'pan', value: 'Pan' },
      { id: 'full_screen', value: 'Full Screen' }
    ],
  },
];</code></pre>

        <div class="footer">
            تولید شده توسط سیستم مستندسازی خودکار | کلیه حقوق محفوظ است &copy; ۱۴۰۵
        </div>
    </div>
</body>
</html>

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Resources\XResourceIDS\XResourceIDs.ts

//
//#region Resource IDS ...
export const AvailableTranslationResources: XLanguageDescriptor[] = [
  {
    name: 'persian',
    locale: 'fa-IR',
    language: 'fa',
    direction: 'rtl',
    resources: [
      //
      //#region XResourceIDS ...
      { id: 'app_name', value: 'ساحر علم' },
      { id: 'company', value: 'فن آوران ساحر علم' },
      { id: 'id', value: 'شناسه' },
      { id: 'file_name', value: 'نام فایل' },
      { id: 'error', value: 'خطا' },
      { id: 'required', value: 'الزامی است.' },
      { id: 'loading', value: 'بارگزاری' },
      { id: 'username', value: 'نام کاربری' },
      { id: 'password', value: 'کلمه عبور' },
      { id: 'first_name', value: 'نام' },
      { id: 'last_name', value: 'نام خانوادگی' },
      { id: 'verified', value: 'تایید شده' },
      { id: 'unverified', value: 'تایید نشده' },
      { id: 'thumbnail', value: 'پیش نمایش' },
      { id: 'language', value: 'زبان' },
      { id: 'take_screenshot', value: 'تصویر برداری ازمحیط' },
      { id: 'enable_capture_mode', value: 'فعالسازی حالت تصویر برداری' },
      { id: 'disable_capture_mode', value: 'غیرفعالسازی حالت تصویر برداری' },
      { id: 'be', value: 'باشد' },
      { id: 'not_to_be', value: 'نباشد' },
      { id: 'enabled', value: 'فعال' },
      { id: 'disabled', value: 'غیر فعال' },
      { id: 'banned', value: 'مسدود' },
      { id: 'un_banned', value: 'غیر مسدود' },
      { id: 'audio', value: 'صوت' },
      { id: 'video', value: 'فیلم' },
      { id: 'cover_image', value: 'تصویر' },
      { id: 'image', value: 'تصویر' },
      { id: 'unknown', value: 'ناشناس' },
      { id: 'location', value: 'موقعیت جغرافیای' },
      { id: 'are_you_sure', value: 'اطمینان دارید ؟' },
      { id: 'success_msg', value: 'عملیات با موفقیت انجام شد' },
      { id: 'locked_msg', value: 'برای ترک این صفحه باید فعالیت جاری را لغو کرده و یا به اتمام برسانید' },
      { id: 'empty_content', value: 'خالی است' },
      { id: 'no_item', value: 'محتوایی ثبت نشده است' },
      { id: 'drag_drop_here', value: 'محل رها کردن فایل' },
      { id: 'map_dialog_title', value: 'نمایش موقعیت مکانی' },
      { id: 'map_dialog_select_title', value: 'انتخاب موقعیت جغرافیایی' },
      { id: 'color_picker_dialog_title', value: 'انتخاب رنگ' },
      { id: 'dismiss', value: 'رد کردن' },
      { id: 'accept', value: 'می پذیرم' },
      { id: 'ok', value: 'بسیار خب' },
      { id: 'save', value: 'ذخیره' },
      { id: 'cancel', value: 'لغو' },
      { id: 'retry', value: 'سعی مجدد' },
      { id: 'edit', value: 'ویرایش' },
      { id: 'reset', value: 'باز نشاندن' },
      { id: 'change', value: 'تغییر' },
      { id: 'start', value: 'آغاز' },
      { id: 'yes', value: 'بله' },
      { id: 'no', value: 'خیر' },
      { id: 'verify', value: 'تایید کردن' },
      { id: 'confirm', value: 'تاییدیه' },
      { id: 'crop', value: 'برش' },
      { id: 'clear', value: 'پاک کردن' },
      { id: 'set', value: 'تنظیم' },
      { id: 'select', value: 'انتخاب' },
      { id: 'send', value: 'ارسال' },
      { id: 'show', value: 'نمایش دادن' },
      { id: 'hide', value: 'پنهان کردن' },
      { id: 'search', value: 'جستجو' },
      { id: 'delete', value: 'حذف' },
      { id: 'clone', value: 'نمونه گیری' },
      { id: 'select_all', value: 'انتخاب همه' },
      { id: 'select_file', value: 'انتخاب فایل(ها)' },
      { id: 'select_all_group', value: 'انتخاب همه موارد فیلتر شده' },
      { id: 'deselect_all', value: 'لغو همه انتخاب ها' },
      { id: 'deselect_all_group', value: 'لغو انتخاب همه موارد فیلتر شده' },
      { id: 'inverse_selection', value: 'انتخاب معکوس' },
      { id: 'selected_location', value: 'انتخاب محل' },
      { id: 'preset_colors', value: 'رنگ های پیش فرض' },
      { id: 'copy_to_clipboard', value: 'ثبت در حافظه موقت' },
      { id: 'color', value: 'رنگ' },
      { id: 'colors', value: 'رنگها' },
      { id: 'upload', value: 'بارگزاری' },
      { id: 'import', value: 'وارد کردن' },
      { id: 'continue', value: 'ادامه' },
      { id: 'finish', value: 'پایان' },
      { id: 'enable', value: 'فعال' },
      { id: 'disable', value: 'غیر فعال' },
      { id: 'all', value: 'همه' },
      { id: 'new', value: 'جدید' },
      { id: 'add', value: 'افزودن' },
      { id: 'update', value: 'بروز رسانی' },
      { id: 'details', value: 'جزئیات' },
      { id: 'translation', value: 'ترجمه' },
      { id: 'download', value: 'دریافت' },
      { id: 'list', value: 'فهرست' },
      { id: 'options', value: 'انتخاب ها' },
      { id: 'descriptions', value: 'توضیحات' },
      { id: 'type', value: 'نوع' },
      { id: 'media', value: 'رسانه' },
      { id: 'medias', value: 'رسانه ها' },
      { id: 'summary', value: 'خلاصه' },
      { id: 'go', value: 'رفتن' },
      { id: 'input', value: 'ورود' },
      { id: 'preview', value: 'پیش نمایش' },
      { id: 'close', value: 'بستن' },
      { id: 'undo', value: 'لغو کردن' },
      { id: 'redo', value: 'انجام دوباره' },
      { id: 'refresh', value: 'بازیابی' },
      { id: 'submit', value: 'ثبت' },
      { id: 'first', value: 'نخستین' },
      { id: 'last', value: 'آخرین' },
      { id: 'previous', value: 'قبلی' },
      { id: 'next', value: 'بعدی' },
      { id: 'back', value: 'بازگشت' },
      { id: 'x_markdown_preview', value: 'پیش نمایش' },
      { id: 'x_markdown_image', value: 'تصویر' },
      { id: 'x_markdown_link', value: 'پیوند' },
      { id: 'x_markdown_ol', value: 'فهرست ترتیبی' },
      { id: 'x_markdown_ul', value: 'فهرست بدون ترتیب' },
      { id: 'x_markdown_quote', value: 'نقل قول' },
      { id: 'x_markdown_code', value: 'کد منبع' },
      { id: 'x_markdown_heading', value: 'عنوان' },
      { id: 'x_markdown_italic', value: 'ایتالیک' },
      { id: 'x_markdown_bold', value: 'پر رنگ' },
      { id: 'x_markdown_guide', value: 'راهنمای نگارش' },
      { id: 'theme', value: 'پوسته' },
      { id: 'theme_mode', value: 'حالت نمایشی' },
      { id: 'theme_dark', value: 'تیره' },
      { id: 'theme_light', value: 'روشن' },
      { id: 'theme_name', value: 'نام پوسته' },
      { id: 'theme_name_description', value: 'نام پوسته را وارد کنید' },
      { id: 'theme_clone_title', value: 'نمونه گیری از پوسته' },
      { id: 'input_color_title', value: 'تعیین رنگ' },
      { id: 'input_color_description', value: 'مقدار رنگ را وارد کنید' },
      { id: 'ide_mode', value: 'نوع کد' },
      { id: 'ide_format', value: 'حالت دهی کد' },
      { id: 'print', value: 'چاپ' },
      { id: 'export_png', value: 'خروجی PNG' },
      { id: 'export_svg', value: 'خروجی SVG' },
      { id: 'export_csv', value: 'خروجی CSV' },
      { id: 'zoom_in', value: 'بزرگنمایی' },
      { id: 'zoom_out', value: 'کوچک نمایی' },
      { id: 'zoom_selection', value: 'بزرگنمایی ناحیه' },
      { id: 'x_axis', value: 'محور افقی' },
      { id: 'y_axis', value: 'محور عمودی' },
      { id: 'xy_axis', value: 'محورهای عمودی و افقی' },
      { id: 'pan', value: 'حرکت آزاد' },
      { id: 'full_screen', value: 'حالت تمام صفحه' }
      //#endregion 
    ],
  },
  {
    name: 'english',
    locale: 'en-US',
    language: 'en',
    direction: 'ltr',
    resources: [
      //
      //#region XResourceIDS ...
      { id: 'app_name', value: 'SaherElm' },
      { id: 'company', value: 'SaherElm IT Center' },
      { id: 'id', value: 'ID' },
      { id: 'file_name', value: 'File Name' },
      { id: 'error', value: 'Error' },
      { id: 'required', value: 'required' },
      { id: 'loading', value: 'loading' },
      { id: 'username', value: 'Username' },
      { id: 'password', value: 'Password' },
      { id: 'first_name', value: 'Firstname' },
      { id: 'last_name', value: 'Lastname' },
      { id: 'verified', value: 'Verified' },
      { id: 'unverified', value: 'Unverified' },
      { id: 'thumbnail', value: 'Thumbnail' },
      { id: 'language', value: 'Language' },
      { id: 'take_screenshot', value: 'Take Screenshot' },
      { id: 'enable_capture_mode', value: 'Enable Capture Mode' },
      { id: 'disable_capture_mode', value: 'Disable Capture Mode' },
      { id: 'be', value: 'Be' },
      { id: 'not_to_be', value: 'Not to be' },
      { id: 'enabled', value: 'Enabled' },
      { id: 'disabled', value: 'Disabled' },
      { id: 'banned', value: 'Banned' },
      { id: 'un_banned', value: 'UnBanned' },
      { id: 'audio', value: 'Audio' },
      { id: 'video', value: 'Video' },
      { id: 'cover_image', value: 'Cover Image' },
      { id: 'image', value: 'Image' },
      { id: 'avatar', value: 'Avatar' },
      { id: 'unknown', value: 'Unknown' },
      { id: 'location', value: 'Location' },
      { id: 'are_you_sure', value: 'Are You Sure ?' },
      { id: 'success_msg', value: 'Operation done Successfully ...' },
      { id: 'locked_msg', value: 'You have to finish/cancel current proccess for leaving' },
      { id: 'empty_content', value: 'Empty' },
      { id: 'no_item', value: 'there is no Content' },
      { id: 'drag_drop_here', value: 'Drop File Here' },
      { id: 'map_dialog_title', value: 'Show Location' },
      { id: 'map_dialog_select_title', value: 'Select Location' },
      { id: 'color_picker_dialog_title', value: 'Select Color' },
      { id: 'dismiss', value: 'dismiss' },
      { id: 'accept', value: 'Accept' },
      { id: 'ok', value: 'Ok' },
      { id: 'save', value: 'Save' },
      { id: 'cancel', value: 'Cancel' },
      { id: 'retry', value: 'Retry' },
      { id: 'edit', value: 'Edit' },
      { id: 'reset', value: 'Reset' },
      { id: 'change', value: 'Change' },
      { id: 'start', value: 'Start' },
      { id: 'yes', value: 'Yes' },
      { id: 'no', value: 'No' },
      { id: 'verify', value: 'Verify' },
      { id: 'confirm', value: 'Confirm' },
      { id: 'crop', value: 'Crop' },
      { id: 'clear', value: 'Clear' },
      { id: 'set', value: 'Set' },
      { id: 'select', value: 'Select' },
      { id: 'send', value: 'Send' },
      { id: 'show', value: 'Show' },
      { id: 'hide', value: 'Hide' },
      { id: 'search', value: 'Search' },
      { id: 'delete', value: 'Delete' },
      { id: 'clone', value: 'Clone' },
      { id: 'select_all', value: 'Select All' },
      { id: 'select_file', value: 'Select File(s)' },
      { id: 'select_all_group', value: 'Select All Filtered Items' },
      { id: 'deselect_all', value: 'Deselect All' },
      { id: 'deselect_all_group', value: 'Deselect All Filtered Items' },
      { id: 'inverse_selection', value: 'Inverse Selection' },
      { id: 'selected_location', value: 'Selected Location' },
      { id: 'preset_colors', value: 'Preset Colors' },
      { id: 'copy_to_clipboard', value: 'Copy to Clipboard' },
      { id: 'color', value: 'Color' },
      { id: 'colors', value: 'Colors' },
      { id: 'upload', value: 'Upload' },
      { id: 'import', value: 'Import' },
      { id: 'continue', value: 'Continue' },
      { id: 'finish', value: 'Finish' },
      { id: 'enable', value: 'Enable' },
      { id: 'disable', value: 'Disable' },
      { id: 'all', value: 'All' },
      { id: 'new', value: 'New' },
      { id: 'add', value: 'Add' },
      { id: 'update', value: 'Update' },
      { id: 'details', value: 'Details' },
      { id: 'translation', value: 'Translation' },
      { id: 'download', value: 'Download' },
      { id: 'list', value: 'List' },
      { id: 'options', value: 'Options' },
      { id: 'descriptions', value: 'Description' },
      { id: 'type', value: 'Type' },
      { id: 'media', value: 'Media' },
      { id: 'medias', value: 'Medias' },
      { id: 'summary', value: 'Summary' },
      { id: 'go', value: 'Go' },
      { id: 'input', value: 'Input' },
      { id: 'preview', value: 'Preview' },
      { id: 'close', value: 'Close' },
      { id: 'undo', value: 'Undo' },
      { id: 'redo', value: 'Redo' },
      { id: 'refresh', value: 'Refresh' },
      { id: 'submit', value: 'Submit' },
      { id: 'first', value: 'First' },
      { id: 'last', value: 'Last' },
      { id: 'previous', value: 'Previous' },
      { id: 'next', value: 'Next' },
      { id: 'back', value: 'Back' },
      { id: 'x_markdown_preview', value: 'Preview' },
      { id: 'x_markdown_image', value: 'Image' },
      { id: 'x_markdown_link', value: 'Link' },
      { id: 'x_markdown_ol', value: 'Ordered List' },
      { id: 'x_markdown_ul', value: 'Unordered List' },
      { id: 'x_markdown_quote', value: 'Quote' },
      { id: 'x_markdown_code', value: 'Code' },
      { id: 'x_markdown_heading', value: 'Heading' },
      { id: 'x_markdown_italic', value: 'Italic' },
      { id: 'x_markdown_bold', value: 'Bold' },
      { id: 'x_markdown_guide', value: 'Markdown Guide' },
      { id: 'theme', value: 'Theme' },
      { id: 'theme_mode', value: 'Theme Mode' },
      { id: 'theme_dark', value: 'Dark' },
      { id: 'theme_light', value: 'Light' },
      { id: 'theme_name', value: 'Theme Name' },
      { id: 'theme_name_description', value: 'Enter Theme name' },
      { id: 'theme_clone_title', value: 'Theme Cloning' },
      { id: 'input_color_title', value: 'Color Value' },
      { id: 'input_color_description', value: 'input color value' },
      { id: 'ide_mode', value: 'Code Style' },
      { id: 'ide_format', value: 'Code Formatting' },
      { id: 'print', value: 'Print' },
      { id: 'export_png', value: 'Export PNG' },
      { id: 'export_svg', value: 'Export SVG' },
      { id: 'export_csv', value: 'Export CSV' },
      { id: 'zoom_in', value: 'Zoom In' },
      { id: 'zoom_out', value: 'Zoom Out' },
      { id: 'zoom_selection', value: 'Zoom Selection' },
      { id: 'x_axis', value: 'X Axis' },
      { id: 'y_axis', value: 'Y Axis' },
      { id: 'xy_axis', value: 'XY Axis' },
      { id: 'pan', value: 'Pan' },
      { id: 'full_screen', value: 'Full Screen' }
      //#endregion
    ],
  },
];
//#endregion 

//
//#region Exception IDS ...
export const AvailableTranslationResources: XLanguageDescriptor[] = [
  {
    name: 'persian',
    locale: 'fa-IR',
    language: 'fa',
    direction: 'rtl',
    resources: [
      //
      //#region Exception Resource IDS ...
      {
        id: 'ex_100',
        value: 'داده نا معتبر',
      },
      {
        id: 'ex_101',
        value: 'تاریخ نامعتبر',
      },
      {
        id: 'ex_102',
        value: 'درخواست اشتباه',
      },
      {
        id: 'ex_103',
        value: 'ورودی نامعتبر',
      },
      {
        id: 'ex_104',
        value: 'پیام نامعتبر',
      },
      {
        id: 'ex_105',
        value: 'نشانه نامعتبر',
      },
      {
        id: 'ex_106',
        value: 'کد اععتبارسنجی نامعتبر',
      },
      {
        id: 'ex_107',
        value: 'پست الکترونیک نامعتبر',
      },
      {
        id: 'ex_108',
        value: 'شماره تلفن همراه نامعتبر',
      },
      {
        id: 'ex_109',
        value: 'کلمه عبور نامعتبر',
      },
      {
        id: 'ex_110',
        value: 'آدرس اینترنتی نامعتبر',
      },
      {
        id: 'ex_111',
        value: 'دستگاه نامعتبر',
      },
      {
        id: 'ex_112',
        value: 'تصویر پروفایل نامعتبر',
      },
      {
        id: 'ex_113',
        value: 'فایل نامعتبر',
      },
      {
        id: 'ex_114',
        value: 'نام کاربری نامعتبر',
      },
      {
        id: 'ex_200',
        value: 'یافت نشد',
      },
      {
        id: 'ex_201',
        value: 'عدم تایید جواز',
      },
      {
        id: 'ex_202',
        value: 'سطح دسترسی نامعتبر',
      },
      {
        id: 'ex_203',
        value: 'غیر قابل نمایش',
      },
      {
        id: 'ex_300',
        value: 'حساب کاربری قفل شده است',
      },
      {
        id: 'ex_301',
        value: 'خطا در ورود',
      },
      {
        id: 'ex_302',
        value: 'کاربر قبلا ثبت نام کرده است',
      },
      {
        id: 'ex_303',
        value: 'کاربر غیرفعال است',
      },
      {
        id: 'ex_304',
        value: 'دستگاه شما قفل شده است تا : (0)',
      },
      {
        id: 'ex_305',
        value: 'امکان درخواست کد اعتبارسنجی وجود ندارد تا : (0)',
      },
      {
        id: 'ex_306',
        value: 'کلمه عبورها یکسان هستند',
      },
      {
        id: 'ex_307',
        value: 'شماره های تلفن یکسان هستند',
      },
      {
        id: 'ex_308',
        value: 'آدرس های پست الکترونیک یکسان هستند',
      },
      {
        id: 'ex_309',
        value: 'شماره تلفن قبلا ثبت شده است',
      },
      {
        id: 'ex_310',
        value: 'پست الکترونیک قبلا ثبت شده است',
      },
      {
        id: 'ex_311',
        value: 'تلفن همراه تایید نشده است',
      },
      {
        id: 'ex_312',
        value: 'پست الکترونیک تایید نشده است',
      },
      {
        id: 'ex_313',
        value: 'مطلب ممنوعه',
      },
      {
        id: 'ex_314',
        value: 'کاربر ممنوعه',
      },
      {
        id: 'ex_400',
        value: 'خطای نا شناخته',
      },
      {
        id: 'ex_401',
        value: 'مورد تکراری',
      },
      {
        id: 'ex_402',
        value: 'شکست عملیات',
      },
      {
        id: 'ex_403',
        value: 'تاریخ مصرف منقضی شده است',
      },
      {
        id: 'ex_404',
        value: 'غیر قابل اسفاده',
      },
      {
        id: 'ex_405',
        value: 'تعداد برچسب ها بیش از جد مجاز است',
      },
      {
        id: 'ex_406',
        value: 'اتمام مهلت زمانی',
      },
      {
        id: 'ex_500',
        value: 'پرونده خالی است',
      },
      {
        id: 'ex_501',
        value: 'اندازه بیش از حد مجاز',
      },
      {
        id: 'ex_502',
        value: 'گونه نامعتبر فایل',
      },
      {
        id: 'ex_503',
        value: 'تعداد رسانه ها بیش از حد مجاز است',
      },
      {
        id: 'ex_600',
        value: 'پیکربندی اشتباه',
      },
      {
        id: 'ex_601',
        value: 'خطا در پیکربندی سامانه ذخیره سازی',
      },
      {
        id: 'ex_602',
        value: 'خطا در پیکربندی سامانه ذخیره سازی',
      },
      {
        id: 'ex_900',
        value: 'تعداد فایل ها بیش از حد مجاز است',
      },
      {
        id: 'ex_901',
        value: 'مجموع اندازه فایل ها کمتر از حد مجاز است',
      },
      {
        id: 'ex_902',
        value: 'مجموع اندازه فایل ها بیش از حد مجاز است',
      },
      {
        id: 'ex_903',
        value: 'شرایط و ضوابط باید پذیرفته شوند',
      },
      {
        id: 'ex_904',
        value: 'عدم پشتیبانی از سیستم موقعیت یاب',
      },
      {
        id: 'ex_905',
        value: 'مجوز دسترسی به موقعیت خود را صادر نکرده اید',
      },
      {
        id: 'ex_906',
        value: 'دسترسی به موقعیت جغرافیایی امکان پذیر نیست',
      },
      {
        id: 'ex_907',
        value: 'عدم پشتیبانی از سیستم ذخیره سازی',
      },
      {
        id: 'ex_908',
        value: 'برای دسترسی به محتوا باید از سامانه خارج شوید',
      },
      {
        id: 'ex_909',
        value: 'زبان انتخاب شده نامعتبر است',
      },
      {
        id: 'ex_910',
        value: 'داده نامعتبر برای انتخاب کاربر',
      },
      //#endregion
    ],
  },
  {
    name: 'english',
    locale: 'en-US',
    language: 'en',
    direction: 'ltr',
    resources: [
      //
      //#region Exception Resource IDS ...
      {
        id: 'ex_100',
        value: 'Invalid Data ...',
      },
      {
        id: 'ex_101',
        value: 'Invalid Date ...',
      },
      {
        id: 'ex_102',
        value: 'Bad Request ...',
      },
      {
        id: 'ex_103',
        value: 'Invalid Args ...',
      },
      {
        id: 'ex_104',
        value: 'Invalid Message ...',
      },
      {
        id: 'ex_105',
        value: 'Invalid Token ...',
      },
      {
        id: 'ex_106',
        value: 'Invalid Verification Code ...',
      },
      {
        id: 'ex_107',
        value: 'Invalid Email Address ...',
      },
      {
        id: 'ex_108',
        value: 'Invalid Mobile Number ...',
      },
      {
        id: 'ex_109',
        value: 'Invalid Password ...',
      },
      {
        id: 'ex_110',
        value: 'Invalid URL ...',
      },
      {
        id: 'ex_111',
        value: 'Invalid Device ...',
      },
      {
        id: 'ex_112',
        value: 'Invalid Profile Image ...',
      },
      {
        id: 'ex_113',
        value: 'Invalid File ...',
      },
      {
        id: 'ex_114',
        value: 'Invalid Username ...',
      },
      {
        id: 'ex_200',
        value: 'Not Found ...',
      },
      {
        id: 'ex_201',
        value: 'Not Allowed ...',
      },
      {
        id: 'ex_202',
        value: 'Not Authorized ...',
      },
      {
        id: 'ex_203',
        value: 'Not Visible ...',
      },
      {
        id: 'ex_300',
        value: 'Account Locked out ...',
      },
      {
        id: 'ex_301',
        value: 'Login Failed ...',
      },
      {
        id: 'ex_302',
        value: 'User Registered Before ...',
      },
      {
        id: 'ex_303',
        value: 'User Disabled ...',
      },
      {
        id: 'ex_304',
        value: 'Device Banned Until:(0)',
      },
      {
        id: 'ex_305',
        value: `You can not Request Verification code Until:(0)`,
      },
      {
        id: 'ex_306',
        value: 'Passwords Same ...',
      },
      {
        id: 'ex_307',
        value: 'Mobiles Same ...',
      },
      {
        id: 'ex_308',
        value: 'Emails Same ...',
      },
      {
        id: 'ex_309',
        value: 'Mobile In Used ...',
      },
      {
        id: 'ex_310',
        value: 'Email In Used ...',
      },
      {
        id: 'ex_311',
        value: 'Mobile Not Confirmed ...',
      },
      {
        id: 'ex_312',
        value: 'Email Not Confirmed ...',
      },
      {
        id: 'ex_313',
        value: 'Post Banned ...',
      },
      {
        id: 'ex_314',
        value: 'User Banned ...',
      },
      {
        id: 'ex_400',
        value: 'Unknown Error ...',
      },
      {
        id: 'ex_401',
        value: 'Duplicate ...',
      },
      {
        id: 'ex_402',
        value: 'Action Failed ...',
      },
      {
        id: 'ex_403',
        value: 'Expired Date ...',
      },
      {
        id: 'ex_404',
        value: 'Unavailable ...',
      },
      {
        id: 'ex_405',
        value: 'Max Allowed Tag Exceeded ...',
      },
      {
        id: 'ex_406',
        value: 'Timeout ...',
      },
      {
        id: 'ex_500',
        value: 'Empty File ...',
      },
      {
        id: 'ex_501',
        value: 'max File size exceeded ...',
      },
      {
        id: 'ex_502',
        value: 'Unsupported File Type ...',
      },
      {
        id: 'ex_503',
        value: 'Max Allowed Media Exceeded ...',
      },
      {
        id: 'ex_600',
        value: 'Invalid Configuration ...',
      },
      {
        id: 'ex_601',
        value: 'Storage Service Initiallization Failed ...',
      },
      {
        id: 'ex_602',
        value: 'Message Service Initialization Failed ...',
      },
      {
        id: 'ex_900',
        value: 'Max Allowed Files Exceeded ...',
      },
      {
        id: 'ex_901',
        value: 'Min Files Size Exceeded ...',
      },
      {
        id: 'ex_902',
        value: 'Max Files Size Exceeded ...',
      },
      {
        id: 'ex_903',
        value: 'Must Accept Terms And Conditions ...',
      },
      {
        id: 'ex_904',
        value: 'Browser does not support Location Services ...',
      },
      {
        id: 'ex_905',
        value: 'You have rejected access to your Location ...',
      },
      {
        id: 'ex_906',
        value: 'Unable to determine your Location ...',
      },
      {
        id: 'ex_907',
        value: 'Browser does not support Storage Services ...',
      },
      {
        id: 'ex_908',
        value: 'Must Logout to access content ...',
      },
      {
        id: 'ex_909',
        value: 'Invalid Locale ...',
      },
      {
        id: 'ex_910',
        value: 'Invalid User Selector ...',
      },
      //#endregion
    ],
  },
];
//#endregion

//
//#region Resource and Exception IDS ...
export const AvailableTranslationResources: XLanguageDescriptor[] = [
  {
    name: 'persian',
    locale: 'fa-IR',
    language: 'fa',
    direction: 'rtl',
    resources: [
      //
      //#region XResourceIDS ...
      { id: 'app_name', value: 'ساحر علم' },
      { id: 'company', value: 'فن آوران ساحر علم' },
      { id: 'id', value: 'شناسه' },
      { id: 'file_name', value: 'نام فایل' },
      { id: 'error', value: 'خطا' },
      { id: 'required', value: 'الزامی است.' },
      { id: 'loading', value: 'بارگزاری' },
      { id: 'username', value: 'نام کاربری' },
      { id: 'password', value: 'کلمه عبور' },
      { id: 'first_name', value: 'نام' },
      { id: 'last_name', value: 'نام خانوادگی' },
      { id: 'verified', value: 'تایید شده' },
      { id: 'unverified', value: 'تایید نشده' },
      { id: 'thumbnail', value: 'پیش نمایش' },
      { id: 'language', value: 'زبان' },
      { id: 'take_screenshot', value: 'تصویر برداری ازمحیط' },
      { id: 'enable_capture_mode', value: 'فعالسازی حالت تصویر برداری' },
      { id: 'disable_capture_mode', value: 'غیرفعالسازی حالت تصویر برداری' },
      { id: 'be', value: 'باشد' },
      { id: 'not_to_be', value: 'نباشد' },
      { id: 'enabled', value: 'فعال' },
      { id: 'disabled', value: 'غیر فعال' },
      { id: 'banned', value: 'مسدود' },
      { id: 'un_banned', value: 'غیر مسدود' },
      { id: 'audio', value: 'صوت' },
      { id: 'video', value: 'فیلم' },
      { id: 'cover_image', value: 'تصویر' },
      { id: 'image', value: 'تصویر' },
      { id: 'unknown', value: 'ناشناس' },
      { id: 'location', value: 'موقعیت جغرافیای' },
      { id: 'are_you_sure', value: 'اطمینان دارید ؟' },
      { id: 'success_msg', value: 'عملیات با موفقیت انجام شد' },
      { id: 'locked_msg', value: 'برای ترک این صفحه باید فعالیت جاری را لغو کرده و یا به اتمام برسانید' },
      { id: 'empty_content', value: 'خالی است' },
      { id: 'no_item', value: 'محتوایی ثبت نشده است' },
      { id: 'drag_drop_here', value: 'محل رها کردن فایل' },
      { id: 'map_dialog_title', value: 'نمایش موقعیت مکانی' },
      { id: 'map_dialog_select_title', value: 'انتخاب موقعیت جغرافیایی' },
      { id: 'color_picker_dialog_title', value: 'انتخاب رنگ' },
      { id: 'dismiss', value: 'رد کردن' },
      { id: 'accept', value: 'می پذیرم' },
      { id: 'ok', value: 'بسیار خب' },
      { id: 'save', value: 'ذخیره' },
      { id: 'cancel', value: 'لغو' },
      { id: 'retry', value: 'سعی مجدد' },
      { id: 'edit', value: 'ویرایش' },
      { id: 'reset', value: 'باز نشاندن' },
      { id: 'change', value: 'تغییر' },
      { id: 'start', value: 'آغاز' },
      { id: 'yes', value: 'بله' },
      { id: 'no', value: 'خیر' },
      { id: 'verify', value: 'تایید کردن' },
      { id: 'confirm', value: 'تاییدیه' },
      { id: 'crop', value: 'برش' },
      { id: 'clear', value: 'پاک کردن' },
      { id: 'set', value: 'تنظیم' },
      { id: 'select', value: 'انتخاب' },
      { id: 'send', value: 'ارسال' },
      { id: 'show', value: 'نمایش دادن' },
      { id: 'hide', value: 'پنهان کردن' },
      { id: 'search', value: 'جستجو' },
      { id: 'delete', value: 'حذف' },
      { id: 'clone', value: 'نمونه گیری' },
      { id: 'select_all', value: 'انتخاب همه' },
      { id: 'select_file', value: 'انتخاب فایل(ها)' },
      { id: 'select_all_group', value: 'انتخاب همه موارد فیلتر شده' },
      { id: 'deselect_all', value: 'لغو همه انتخاب ها' },
      { id: 'deselect_all_group', value: 'لغو انتخاب همه موارد فیلتر شده' },
      { id: 'inverse_selection', value: 'انتخاب معکوس' },
      { id: 'selected_location', value: 'انتخاب محل' },
      { id: 'preset_colors', value: 'رنگ های پیش فرض' },
      { id: 'copy_to_clipboard', value: 'ثبت در حافظه موقت' },
      { id: 'color', value: 'رنگ' },
      { id: 'colors', value: 'رنگها' },
      { id: 'upload', value: 'بارگزاری' },
      { id: 'import', value: 'وارد کردن' },
      { id: 'continue', value: 'ادامه' },
      { id: 'finish', value: 'پایان' },
      { id: 'enable', value: 'فعال' },
      { id: 'disable', value: 'غیر فعال' },
      { id: 'all', value: 'همه' },
      { id: 'new', value: 'جدید' },
      { id: 'add', value: 'افزودن' },
      { id: 'update', value: 'بروز رسانی' },
      { id: 'details', value: 'جزئیات' },
      { id: 'translation', value: 'ترجمه' },
      { id: 'download', value: 'دریافت' },
      { id: 'list', value: 'فهرست' },
      { id: 'options', value: 'انتخاب ها' },
      { id: 'descriptions', value: 'توضیحات' },
      { id: 'type', value: 'نوع' },
      { id: 'media', value: 'رسانه' },
      { id: 'medias', value: 'رسانه ها' },
      { id: 'summary', value: 'خلاصه' },
      { id: 'go', value: 'رفتن' },
      { id: 'input', value: 'ورود' },
      { id: 'preview', value: 'پیش نمایش' },
      { id: 'close', value: 'بستن' },
      { id: 'undo', value: 'لغو کردن' },
      { id: 'redo', value: 'انجام دوباره' },
      { id: 'refresh', value: 'بازیابی' },
      { id: 'submit', value: 'ثبت' },
      { id: 'first', value: 'نخستین' },
      { id: 'last', value: 'آخرین' },
      { id: 'previous', value: 'قبلی' },
      { id: 'next', value: 'بعدی' },
      { id: 'back', value: 'بازگشت' },
      { id: 'x_markdown_preview', value: 'پیش نمایش' },
      { id: 'x_markdown_image', value: 'تصویر' },
      { id: 'x_markdown_link', value: 'پیوند' },
      { id: 'x_markdown_ol', value: 'فهرست ترتیبی' },
      { id: 'x_markdown_ul', value: 'فهرست بدون ترتیب' },
      { id: 'x_markdown_quote', value: 'نقل قول' },
      { id: 'x_markdown_code', value: 'کد منبع' },
      { id: 'x_markdown_heading', value: 'عنوان' },
      { id: 'x_markdown_italic', value: 'ایتالیک' },
      { id: 'x_markdown_bold', value: 'پر رنگ' },
      { id: 'x_markdown_guide', value: 'راهنمای نگارش' },
      { id: 'theme', value: 'پوسته' },
      { id: 'theme_mode', value: 'حالت نمایشی' },
      { id: 'theme_dark', value: 'تیره' },
      { id: 'theme_light', value: 'روشن' },
      { id: 'theme_name', value: 'نام پوسته' },
      { id: 'theme_name_description', value: 'نام پوسته را وارد کنید' },
      { id: 'theme_clone_title', value: 'نمونه گیری از پوسته' },
      { id: 'input_color_title', value: 'تعیین رنگ' },
      { id: 'input_color_description', value: 'مقدار رنگ را وارد کنید' },
      { id: 'ide_mode', value: 'نوع کد' },
      { id: 'ide_format', value: 'حالت دهی کد' },
      { id: 'print', value: 'چاپ' },
      { id: 'export_png', value: 'خروجی PNG' },
      { id: 'export_svg', value: 'خروجی SVG' },
      { id: 'export_csv', value: 'خروجی CSV' },
      { id: 'zoom_in', value: 'بزرگنمایی' },
      { id: 'zoom_out', value: 'کوچک نمایی' },
      { id: 'zoom_selection', value: 'بزرگنمایی ناحیه' },
      { id: 'x_axis', value: 'محور افقی' },
      { id: 'y_axis', value: 'محور عمودی' },
      { id: 'xy_axis', value: 'محورهای عمودی و افقی' },
      { id: 'pan', value: 'حرکت آزاد' },
      { id: 'full_screen', value: 'حالت تمام صفحه' }
      //#endregion 

      //
      //#region Exception Resource IDS ...
      {
        id: 'ex_100',
        value: 'داده نا معتبر',
      },
      {
        id: 'ex_101',
        value: 'تاریخ نامعتبر',
      },
      {
        id: 'ex_102',
        value: 'درخواست اشتباه',
      },
      {
        id: 'ex_103',
        value: 'ورودی نامعتبر',
      },
      {
        id: 'ex_104',
        value: 'پیام نامعتبر',
      },
      {
        id: 'ex_105',
        value: 'نشانه نامعتبر',
      },
      {
        id: 'ex_106',
        value: 'کد اععتبارسنجی نامعتبر',
      },
      {
        id: 'ex_107',
        value: 'پست الکترونیک نامعتبر',
      },
      {
        id: 'ex_108',
        value: 'شماره تلفن همراه نامعتبر',
      },
      {
        id: 'ex_109',
        value: 'کلمه عبور نامعتبر',
      },
      {
        id: 'ex_110',
        value: 'آدرس اینترنتی نامعتبر',
      },
      {
        id: 'ex_111',
        value: 'دستگاه نامعتبر',
      },
      {
        id: 'ex_112',
        value: 'تصویر پروفایل نامعتبر',
      },
      {
        id: 'ex_113',
        value: 'فایل نامعتبر',
      },
      {
        id: 'ex_114',
        value: 'نام کاربری نامعتبر',
      },
      {
        id: 'ex_200',
        value: 'یافت نشد',
      },
      {
        id: 'ex_201',
        value: 'عدم تایید جواز',
      },
      {
        id: 'ex_202',
        value: 'سطح دسترسی نامعتبر',
      },
      {
        id: 'ex_203',
        value: 'غیر قابل نمایش',
      },
      {
        id: 'ex_300',
        value: 'حساب کاربری قفل شده است',
      },
      {
        id: 'ex_301',
        value: 'خطا در ورود',
      },
      {
        id: 'ex_302',
        value: 'کاربر قبلا ثبت نام کرده است',
      },
      {
        id: 'ex_303',
        value: 'کاربر غیرفعال است',
      },
      {
        id: 'ex_304',
        value: 'دستگاه شما قفل شده است تا : (0)',
      },
      {
        id: 'ex_305',
        value: 'امکان درخواست کد اعتبارسنجی وجود ندارد تا : (0)',
      },
      {
        id: 'ex_306',
        value: 'کلمه عبورها یکسان هستند',
      },
      {
        id: 'ex_307',
        value: 'شماره های تلفن یکسان هستند',
      },
      {
        id: 'ex_308',
        value: 'آدرس های پست الکترونیک یکسان هستند',
      },
      {
        id: 'ex_309',
        value: 'شماره تلفن قبلا ثبت شده است',
      },
      {
        id: 'ex_310',
        value: 'پست الکترونیک قبلا ثبت شده است',
      },
      {
        id: 'ex_311',
        value: 'تلفن همراه تایید نشده است',
      },
      {
        id: 'ex_312',
        value: 'پست الکترونیک تایید نشده است',
      },
      {
        id: 'ex_313',
        value: 'مطلب ممنوعه',
      },
      {
        id: 'ex_314',
        value: 'کاربر ممنوعه',
      },
      {
        id: 'ex_400',
        value: 'خطای نا شناخته',
      },
      {
        id: 'ex_401',
        value: 'مورد تکراری',
      },
      {
        id: 'ex_402',
        value: 'شکست عملیات',
      },
      {
        id: 'ex_403',
        value: 'تاریخ مصرف منقضی شده است',
      },
      {
        id: 'ex_404',
        value: 'غیر قابل اسفاده',
      },
      {
        id: 'ex_405',
        value: 'تعداد برچسب ها بیش از جد مجاز است',
      },
      {
        id: 'ex_406',
        value: 'اتمام مهلت زمانی',
      },
      {
        id: 'ex_500',
        value: 'پرونده خالی است',
      },
      {
        id: 'ex_501',
        value: 'اندازه بیش از حد مجاز',
      },
      {
        id: 'ex_502',
        value: 'گونه نامعتبر فایل',
      },
      {
        id: 'ex_503',
        value: 'تعداد رسانه ها بیش از حد مجاز است',
      },
      {
        id: 'ex_600',
        value: 'پیکربندی اشتباه',
      },
      {
        id: 'ex_601',
        value: 'خطا در پیکربندی سامانه ذخیره سازی',
      },
      {
        id: 'ex_602',
        value: 'خطا در پیکربندی سامانه ذخیره سازی',
      },
      {
        id: 'ex_900',
        value: 'تعداد فایل ها بیش از حد مجاز است',
      },
      {
        id: 'ex_901',
        value: 'مجموع اندازه فایل ها کمتر از حد مجاز است',
      },
      {
        id: 'ex_902',
        value: 'مجموع اندازه فایل ها بیش از حد مجاز است',
      },
      {
        id: 'ex_903',
        value: 'شرایط و ضوابط باید پذیرفته شوند',
      },
      {
        id: 'ex_904',
        value: 'عدم پشتیبانی از سیستم موقعیت یاب',
      },
      {
        id: 'ex_905',
        value: 'مجوز دسترسی به موقعیت خود را صادر نکرده اید',
      },
      {
        id: 'ex_906',
        value: 'دسترسی به موقعیت جغرافیایی امکان پذیر نیست',
      },
      {
        id: 'ex_907',
        value: 'عدم پشتیبانی از سیستم ذخیره سازی',
      },
      {
        id: 'ex_908',
        value: 'برای دسترسی به محتوا باید از سامانه خارج شوید',
      },
      {
        id: 'ex_909',
        value: 'زبان انتخاب شده نامعتبر است',
      },
      {
        id: 'ex_910',
        value: 'داده نامعتبر برای انتخاب کاربر',
      },
      //#endregion
    ],
  },
  {
    name: 'english',
    locale: 'en-US',
    language: 'en',
    direction: 'ltr',
    resources: [
      //
      //#region XResourceIDS ...
      { id: 'app_name', value: 'SaherElm' },
      { id: 'company', value: 'SaherElm IT Center' },
      { id: 'id', value: 'ID' },
      { id: 'file_name', value: 'File Name' },
      { id: 'error', value: 'Error' },
      { id: 'required', value: 'required' },
      { id: 'loading', value: 'loading' },
      { id: 'username', value: 'Username' },
      { id: 'password', value: 'Password' },
      { id: 'first_name', value: 'Firstname' },
      { id: 'last_name', value: 'Lastname' },
      { id: 'verified', value: 'Verified' },
      { id: 'unverified', value: 'Unverified' },
      { id: 'thumbnail', value: 'Thumbnail' },
      { id: 'language', value: 'Language' },
      { id: 'take_screenshot', value: 'Take Screenshot' },
      { id: 'enable_capture_mode', value: 'Enable Capture Mode' },
      { id: 'disable_capture_mode', value: 'Disable Capture Mode' },
      { id: 'be', value: 'Be' },
      { id: 'not_to_be', value: 'Not to be' },
      { id: 'enabled', value: 'Enabled' },
      { id: 'disabled', value: 'Disabled' },
      { id: 'banned', value: 'Banned' },
      { id: 'un_banned', value: 'UnBanned' },
      { id: 'audio', value: 'Audio' },
      { id: 'video', value: 'Video' },
      { id: 'cover_image', value: 'Cover Image' },
      { id: 'image', value: 'Image' },
      { id: 'avatar', value: 'Avatar' },
      { id: 'unknown', value: 'Unknown' },
      { id: 'location', value: 'Location' },
      { id: 'are_you_sure', value: 'Are You Sure ?' },
      { id: 'success_msg', value: 'Operation done Successfully ...' },
      { id: 'locked_msg', value: 'You have to finish/cancel current proccess for leaving' },
      { id: 'empty_content', value: 'Empty' },
      { id: 'no_item', value: 'there is no Content' },
      { id: 'drag_drop_here', value: 'Drop File Here' },
      { id: 'map_dialog_title', value: 'Show Location' },
      { id: 'map_dialog_select_title', value: 'Select Location' },
      { id: 'color_picker_dialog_title', value: 'Select Color' },
      { id: 'dismiss', value: 'dismiss' },
      { id: 'accept', value: 'Accept' },
      { id: 'ok', value: 'Ok' },
      { id: 'save', value: 'Save' },
      { id: 'cancel', value: 'Cancel' },
      { id: 'retry', value: 'Retry' },
      { id: 'edit', value: 'Edit' },
      { id: 'reset', value: 'Reset' },
      { id: 'change', value: 'Change' },
      { id: 'start', value: 'Start' },
      { id: 'yes', value: 'Yes' },
      { id: 'no', value: 'No' },
      { id: 'verify', value: 'Verify' },
      { id: 'confirm', value: 'Confirm' },
      { id: 'crop', value: 'Crop' },
      { id: 'clear', value: 'Clear' },
      { id: 'set', value: 'Set' },
      { id: 'select', value: 'Select' },
      { id: 'send', value: 'Send' },
      { id: 'show', value: 'Show' },
      { id: 'hide', value: 'Hide' },
      { id: 'search', value: 'Search' },
      { id: 'delete', value: 'Delete' },
      { id: 'clone', value: 'Clone' },
      { id: 'select_all', value: 'Select All' },
      { id: 'select_file', value: 'Select File(s)' },
      { id: 'select_all_group', value: 'Select All Filtered Items' },
      { id: 'deselect_all', value: 'Deselect All' },
      { id: 'deselect_all_group', value: 'Deselect All Filtered Items' },
      { id: 'inverse_selection', value: 'Inverse Selection' },
      { id: 'selected_location', value: 'Selected Location' },
      { id: 'preset_colors', value: 'Preset Colors' },
      { id: 'copy_to_clipboard', value: 'Copy to Clipboard' },
      { id: 'color', value: 'Color' },
      { id: 'colors', value: 'Colors' },
      { id: 'upload', value: 'Upload' },
      { id: 'import', value: 'Import' },
      { id: 'continue', value: 'Continue' },
      { id: 'finish', value: 'Finish' },
      { id: 'enable', value: 'Enable' },
      { id: 'disable', value: 'Disable' },
      { id: 'all', value: 'All' },
      { id: 'new', value: 'New' },
      { id: 'add', value: 'Add' },
      { id: 'update', value: 'Update' },
      { id: 'details', value: 'Details' },
      { id: 'translation', value: 'Translation' },
      { id: 'download', value: 'Download' },
      { id: 'list', value: 'List' },
      { id: 'options', value: 'Options' },
      { id: 'descriptions', value: 'Description' },
      { id: 'type', value: 'Type' },
      { id: 'media', value: 'Media' },
      { id: 'medias', value: 'Medias' },
      { id: 'summary', value: 'Summary' },
      { id: 'go', value: 'Go' },
      { id: 'input', value: 'Input' },
      { id: 'preview', value: 'Preview' },
      { id: 'close', value: 'Close' },
      { id: 'undo', value: 'Undo' },
      { id: 'redo', value: 'Redo' },
      { id: 'refresh', value: 'Refresh' },
      { id: 'submit', value: 'Submit' },
      { id: 'first', value: 'First' },
      { id: 'last', value: 'Last' },
      { id: 'previous', value: 'Previous' },
      { id: 'next', value: 'Next' },
      { id: 'back', value: 'Back' },
      { id: 'x_markdown_preview', value: 'Preview' },
      { id: 'x_markdown_image', value: 'Image' },
      { id: 'x_markdown_link', value: 'Link' },
      { id: 'x_markdown_ol', value: 'Ordered List' },
      { id: 'x_markdown_ul', value: 'Unordered List' },
      { id: 'x_markdown_quote', value: 'Quote' },
      { id: 'x_markdown_code', value: 'Code' },
      { id: 'x_markdown_heading', value: 'Heading' },
      { id: 'x_markdown_italic', value: 'Italic' },
      { id: 'x_markdown_bold', value: 'Bold' },
      { id: 'x_markdown_guide', value: 'Markdown Guide' },
      { id: 'theme', value: 'Theme' },
      { id: 'theme_mode', value: 'Theme Mode' },
      { id: 'theme_dark', value: 'Dark' },
      { id: 'theme_light', value: 'Light' },
      { id: 'theme_name', value: 'Theme Name' },
      { id: 'theme_name_description', value: 'Enter Theme name' },
      { id: 'theme_clone_title', value: 'Theme Cloning' },
      { id: 'input_color_title', value: 'Color Value' },
      { id: 'input_color_description', value: 'input color value' },
      { id: 'ide_mode', value: 'Code Style' },
      { id: 'ide_format', value: 'Code Formatting' },
      { id: 'print', value: 'Print' },
      { id: 'export_png', value: 'Export PNG' },
      { id: 'export_svg', value: 'Export SVG' },
      { id: 'export_csv', value: 'Export CSV' },
      { id: 'zoom_in', value: 'Zoom In' },
      { id: 'zoom_out', value: 'Zoom Out' },
      { id: 'zoom_selection', value: 'Zoom Selection' },
      { id: 'x_axis', value: 'X Axis' },
      { id: 'y_axis', value: 'Y Axis' },
      { id: 'xy_axis', value: 'XY Axis' },
      { id: 'pan', value: 'Pan' },
      { id: 'full_screen', value: 'Full Screen' }
      //#endregion

      //
      //#region Exception Resource IDS ...
      {
        id: 'ex_100',
        value: 'Invalid Data ...',
      },
      {
        id: 'ex_101',
        value: 'Invalid Date ...',
      },
      {
        id: 'ex_102',
        value: 'Bad Request ...',
      },
      {
        id: 'ex_103',
        value: 'Invalid Args ...',
      },
      {
        id: 'ex_104',
        value: 'Invalid Message ...',
      },
      {
        id: 'ex_105',
        value: 'Invalid Token ...',
      },
      {
        id: 'ex_106',
        value: 'Invalid Verification Code ...',
      },
      {
        id: 'ex_107',
        value: 'Invalid Email Address ...',
      },
      {
        id: 'ex_108',
        value: 'Invalid Mobile Number ...',
      },
      {
        id: 'ex_109',
        value: 'Invalid Password ...',
      },
      {
        id: 'ex_110',
        value: 'Invalid URL ...',
      },
      {
        id: 'ex_111',
        value: 'Invalid Device ...',
      },
      {
        id: 'ex_112',
        value: 'Invalid Profile Image ...',
      },
      {
        id: 'ex_113',
        value: 'Invalid File ...',
      },
      {
        id: 'ex_114',
        value: 'Invalid Username ...',
      },
      {
        id: 'ex_200',
        value: 'Not Found ...',
      },
      {
        id: 'ex_201',
        value: 'Not Allowed ...',
      },
      {
        id: 'ex_202',
        value: 'Not Authorized ...',
      },
      {
        id: 'ex_203',
        value: 'Not Visible ...',
      },
      {
        id: 'ex_300',
        value: 'Account Locked out ...',
      },
      {
        id: 'ex_301',
        value: 'Login Failed ...',
      },
      {
        id: 'ex_302',
        value: 'User Registered Before ...',
      },
      {
        id: 'ex_303',
        value: 'User Disabled ...',
      },
      {
        id: 'ex_304',
        value: 'Device Banned Until:(0)',
      },
      {
        id: 'ex_305',
        value: `You can not Request Verification code Until:(0)`,
      },
      {
        id: 'ex_306',
        value: 'Passwords Same ...',
      },
      {
        id: 'ex_307',
        value: 'Mobiles Same ...',
      },
      {
        id: 'ex_308',
        value: 'Emails Same ...',
      },
      {
        id: 'ex_309',
        value: 'Mobile In Used ...',
      },
      {
        id: 'ex_310',
        value: 'Email In Used ...',
      },
      {
        id: 'ex_311',
        value: 'Mobile Not Confirmed ...',
      },
      {
        id: 'ex_312',
        value: 'Email Not Confirmed ...',
      },
      {
        id: 'ex_313',
        value: 'Post Banned ...',
      },
      {
        id: 'ex_314',
        value: 'User Banned ...',
      },
      {
        id: 'ex_400',
        value: 'Unknown Error ...',
      },
      {
        id: 'ex_401',
        value: 'Duplicate ...',
      },
      {
        id: 'ex_402',
        value: 'Action Failed ...',
      },
      {
        id: 'ex_403',
        value: 'Expired Date ...',
      },
      {
        id: 'ex_404',
        value: 'Unavailable ...',
      },
      {
        id: 'ex_405',
        value: 'Max Allowed Tag Exceeded ...',
      },
      {
        id: 'ex_406',
        value: 'Timeout ...',
      },
      {
        id: 'ex_500',
        value: 'Empty File ...',
      },
      {
        id: 'ex_501',
        value: 'max File size exceeded ...',
      },
      {
        id: 'ex_502',
        value: 'Unsupported File Type ...',
      },
      {
        id: 'ex_503',
        value: 'Max Allowed Media Exceeded ...',
      },
      {
        id: 'ex_600',
        value: 'Invalid Configuration ...',
      },
      {
        id: 'ex_601',
        value: 'Storage Service Initiallization Failed ...',
      },
      {
        id: 'ex_602',
        value: 'Message Service Initialization Failed ...',
      },
      {
        id: 'ex_900',
        value: 'Max Allowed Files Exceeded ...',
      },
      {
        id: 'ex_901',
        value: 'Min Files Size Exceeded ...',
      },
      {
        id: 'ex_902',
        value: 'Max Files Size Exceeded ...',
      },
      {
        id: 'ex_903',
        value: 'Must Accept Terms And Conditions ...',
      },
      {
        id: 'ex_904',
        value: 'Browser does not support Location Services ...',
      },
      {
        id: 'ex_905',
        value: 'You have rejected access to your Location ...',
      },
      {
        id: 'ex_906',
        value: 'Unable to determine your Location ...',
      },
      {
        id: 'ex_907',
        value: 'Browser does not support Storage Services ...',
      },
      {
        id: 'ex_908',
        value: 'Must Logout to access content ...',
      },
      {
        id: 'ex_909',
        value: 'Invalid Locale ...',
      },
      {
        id: 'ex_910',
        value: 'Invalid User Selector ...',
      },
      //#endregion
    ],
  },
];
//#endregion

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Resources\XResourceIDS\Generator\xFrameworkResourceManager.html

<!doctype html>
<html lang="fa" dir="rtl">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>مدیریت منابع زبانی xFramework</title>
    <style>
      * {
        box-sizing: border-box;
        margin: 0;
        padding: 0;
      }
      body {
        font-family: "Segoe UI", Tahoma, sans-serif;
        background: linear-gradient(135deg, #1e1e2f 0%, #2a2a3e 100%);
        color: #eee;
        min-height: 100vh;
        padding: 20px;
      }
      header {
        text-align: center;
        padding: 30px 20px;
        background: rgba(255, 255, 255, 0.05);
        border-radius: 16px;
        margin-bottom: 30px;
        backdrop-filter: blur(10px);
      }
      header h1 {
        font-size: 2.2em;
        margin-bottom: 10px;
        color: #7ec8ff;
      }
      header p {
        opacity: 0.8;
      }
      header .meta {
        margin-top: 12px;
        font-size: 0.85em;
        opacity: 0.6;
      }
      .container {
        max-width: 1500px;
        margin: 0 auto;
      }
      .section {
        background: rgba(255, 255, 255, 0.04);
        border-radius: 16px;
        padding: 24px;
        margin-bottom: 24px;
        border: 1px solid rgba(255, 255, 255, 0.08);
      }
      .section h2 {
        margin-bottom: 20px;
        color: #7ec8ff;
        border-bottom: 2px solid rgba(126, 200, 255, 0.3);
        padding-bottom: 10px;
      }

      /* Source Selection */
      .source-tabs {
        display: flex;
        gap: 8px;
        margin-bottom: 20px;
        background: rgba(0, 0, 0, 0.2);
        padding: 6px;
        border-radius: 10px;
      }
      .source-tab {
        flex: 1;
        padding: 12px 16px;
        background: rgba(255, 255, 255, 0.05);
        border-radius: 8px;
        cursor: pointer;
        text-align: center;
        font-weight: 600;
        transition: all 0.2s;
        border: 1px solid transparent;
        font-size: 14px;
      }
      .source-tab:hover {
        background: rgba(255, 255, 255, 0.1);
      }
      .source-tab.active {
        background: rgba(126, 200, 255, 0.15);
        border-color: rgba(126, 200, 255, 0.4);
        color: #7ec8ff;
      }
      .source-content {
        display: none;
      }
      .source-content.active {
        display: block;
      }

      /* Upload */
      .upload-section {
        background: rgba(0, 0, 0, 0.2);
        border: 2px dashed rgba(126, 200, 255, 0.3);
        border-radius: 12px;
        padding: 24px;
        text-align: center;
        transition: all 0.3s;
      }
      .upload-section:hover {
        border-color: rgba(126, 200, 255, 0.6);
        background: rgba(0, 0, 0, 0.3);
      }
      .upload-btn {
        display: inline-block;
        padding: 10px 24px;
        background: #7ec8ff;
        color: #1e1e2f;
        border-radius: 8px;
        cursor: pointer;
        font-weight: 600;
        transition: all 0.2s;
      }
      .upload-btn:hover {
        background: #5ab0f0;
        transform: translateY(-2px);
      }
      .upload-status {
        margin-top: 12px;
        font-size: 13px;
        min-height: 18px;
      }
      .upload-status.success {
        color: #4caf50;
      }
      .upload-status.error {
        color: #f44336;
      }

      /* Language Management */
      .lang-list {
        display: flex;
        flex-wrap: wrap;
        gap: 10px;
        margin-bottom: 16px;
      }
      .lang-chip {
        background: rgba(126, 200, 255, 0.1);
        border: 1px solid rgba(126, 200, 255, 0.3);
        padding: 8px 16px;
        border-radius: 20px;
        font-size: 13px;
        display: flex;
        align-items: center;
        gap: 8px;
      }
      .lang-chip .remove-lang {
        cursor: pointer;
        color: #f44336;
        font-weight: bold;
        margin-right: 4px;
      }
      .add-lang-form {
        display: flex;
        gap: 8px;
        flex-wrap: wrap;
        margin-top: 12px;
        padding: 12px;
        background: rgba(0, 0, 0, 0.2);
        border-radius: 8px;
      }
      .add-lang-form input,
      .add-lang-form select {
        padding: 8px 12px;
        background: rgba(255, 255, 255, 0.08);
        border: 1px solid rgba(255, 255, 255, 0.15);
        border-radius: 6px;
        color: #fff;
        font-size: 13px;
        flex: 1;
        min-width: 120px;
      }

      /* Identifier Editor Layout */
      .editor-layout {
        display: grid;
        grid-template-columns: 350px 1fr;
        gap: 20px;
      }
      @media (max-width: 900px) {
        .editor-layout {
          grid-template-columns: 1fr;
        }
      }

      /* Identifier List */
      .id-list-container {
        max-height: 500px;
        overflow-y: auto;
        background: rgba(0, 0, 0, 0.2);
        border-radius: 10px;
        border: 1px solid rgba(255, 255, 255, 0.05);
      }
      .id-list-container::-webkit-scrollbar {
        width: 8px;
      }
      .id-list-container::-webkit-scrollbar-track {
        background: rgba(0, 0, 0, 0.2);
        border-radius: 4px;
      }
      .id-list-container::-webkit-scrollbar-thumb {
        background: rgba(126, 200, 255, 0.4);
        border-radius: 4px;
      }
      .id-item {
        padding: 10px 14px;
        border-bottom: 1px solid rgba(255, 255, 255, 0.05);
        cursor: pointer;
        transition: background 0.2s;
        display: flex;
        justify-content: space-between;
        align-items: center;
        font-size: 13px;
        font-family: monospace;
      }
      .id-item:hover {
        background: rgba(126, 200, 255, 0.1);
      }
      .id-item.active {
        background: rgba(126, 200, 255, 0.2);
        border-right: 3px solid #7ec8ff;
      }
      .id-item .warning-badge {
        background: #ff9800;
        color: #000;
        font-size: 10px;
        padding: 2px 6px;
        border-radius: 4px;
        font-weight: bold;
      }

      /* Edit Form */
      .edit-form {
        background: rgba(0, 0, 0, 0.2);
        border-radius: 10px;
        padding: 20px;
        border: 1px solid rgba(255, 255, 255, 0.05);
      }
      .edit-row {
        margin-bottom: 16px;
      }
      .edit-row label {
        display: block;
        margin-bottom: 6px;
        color: #a8d0ff;
        font-size: 13px;
        font-weight: 600;
      }
      .edit-row input,
      .edit-row textarea {
        width: 100%;
        padding: 10px 12px;
        background: rgba(255, 255, 255, 0.08);
        border: 1px solid rgba(255, 255, 255, 0.15);
        border-radius: 6px;
        color: #fff;
        font-size: 14px;
        font-family: "Segoe UI", Tahoma, sans-serif;
      }
      .edit-row input:focus,
      .edit-row textarea:focus {
        outline: none;
        border-color: #7ec8ff;
        background: rgba(255, 255, 255, 0.12);
      }
      .edit-row textarea {
        min-height: 80px;
        resize: vertical;
      }

      /* JSON Output */
      .json-output {
        background: #0d1117;
        border-radius: 8px;
        padding: 14px;
        max-height: 380px;
        overflow: auto;
        font-family: "Consolas", monospace;
        font-size: 11px;
        color: #c9d1d9;
        border: 1px solid rgba(255, 255, 255, 0.1);
        direction: ltr;
        text-align: left;
        white-space: pre;
      }
      .json-output::-webkit-scrollbar {
        width: 8px;
        height: 8px;
      }
      .json-output::-webkit-scrollbar-track {
        background: rgba(0, 0, 0, 0.2);
      }
      .json-output::-webkit-scrollbar-thumb {
        background: rgba(126, 200, 255, 0.4);
        border-radius: 4px;
      }
      .tabs {
        display: flex;
        gap: 4px;
        margin-bottom: 12px;
      }
      .tab {
        padding: 8px 16px;
        background: rgba(255, 255, 255, 0.05);
        border-radius: 6px 6px 0 0;
        cursor: pointer;
        border: 1px solid transparent;
        font-size: 13px;
        font-weight: 500;
      }
      .tab.active {
        background: rgba(126, 200, 255, 0.15);
        border-color: rgba(126, 200, 255, 0.3);
        color: #7ec8ff;
      }

      /* Action Buttons */
      .action-buttons {
        display: flex;
        gap: 10px;
        flex-wrap: wrap;
        margin-top: 16px;
        padding-top: 16px;
        border-top: 1px solid rgba(255, 255, 255, 0.1);
      }
      .btn {
        padding: 10px 20px;
        border: none;
        border-radius: 8px;
        cursor: pointer;
        font-weight: 600;
        font-size: 13px;
        transition: all 0.2s;
      }
      .btn-primary {
        background: #7ec8ff;
        color: #1e1e2f;
      }
      .btn-primary:hover {
        background: #5ab0f0;
      }
      .btn-success {
        background: #4caf50;
        color: #fff;
      }
      .btn-success:hover {
        background: #3d9140;
      }
      .btn-warning {
        background: #ff9800;
        color: #fff;
      }
      .btn-warning:hover {
        background: #e08600;
      }
      .btn-info {
        background: #2196f3;
        color: #fff;
      }
      .btn-info:hover {
        background: #1976d2;
      }
      .btn-danger {
        background: #f44336;
        color: #fff;
      }
      .btn-danger:hover {
        background: #d32f2f;
      }

      .search-box {
        width: 100%;
        padding: 10px 14px;
        background: rgba(255, 255, 255, 0.08);
        border: 1px solid rgba(255, 255, 255, 0.15);
        border-radius: 8px;
        color: #fff;
        font-size: 14px;
        margin-bottom: 12px;
      }
      .search-box:focus {
        outline: none;
        border-color: #7ec8ff;
      }
    </style>
  </head>
  <body>
    <header>
      <h1>🌐 مدیریت منابع زبانی xFramework</h1>
      <p>
        مدیریت، ویرایش و اعتبارسنجی فایل‌های ترجمه برای برنامه‌های کاربردی مبتنی
        بر xFramework
      </p>
      <div class="meta">
        توسعه‌دهنده: هادی خزاعی اصل | شرکت: فن آوران ساحر علم | تاریخ: ۱ شهریور
        ۱۴۰۵
      </div>
    </header>

    <div class="container">
      <!-- Source Section -->
      <div class="section">
        <h2>📂 منبع داده‌ها</h2>
        <div class="source-tabs">
          <div class="source-tab active" onclick="switchSource('default')">
            📚 منابع پیش‌فرض سیستم
          </div>
          <div class="source-tab" onclick="switchSource('upload')">
            📁 بارگذاری فایل JSON
          </div>
        </div>

        <div id="source-default" class="source-content active">
          <p style="opacity: 0.7; font-size: 14px; padding: 10px">
            ✅ در حال حاضر از منابع پیش‌فرض xFramework استفاده می‌شود. می‌توانید
            مستقیماً ویرایش را شروع کنید.
          </p>
        </div>

        <div id="source-upload" class="source-content">
          <div class="upload-section">
            <label for="jsonFileInput" class="upload-btn"
              >📁 انتخاب فایل JSON منابع</label
            >
            <input
              type="file"
              id="jsonFileInput"
              accept=".json"
              style="display: none"
              onchange="handleFileUpload(event)"
            />
            <div id="uploadStatus" class="upload-status"></div>
          </div>
        </div>
      </div>

      <!-- Language Management Section -->
      <div class="section">
        <h2>🌍 مدیریت زبان‌ها</h2>
        <div class="lang-list" id="langList"></div>

        <div class="add-lang-form">
          <input
            type="text"
            id="newLangName"
            placeholder="نام زبان (مثال: arabic)"
          />
          <input
            type="text"
            id="newLangLocale"
            placeholder="لوکال (مثال: ar-SA)"
          />
          <input
            type="text"
            id="newLangCode"
            placeholder="کد زبان (مثال: ar)"
          />
          <select id="newLangDir">
            <option value="rtl">RTL</option>
            <option value="ltr">LTR</option>
          </select>
          <button class="btn btn-primary" onclick="addNewLanguage()">
            ➕ افزودن زبان
          </button>
        </div>
      </div>

      <!-- Identifier Editor Section -->
      <div class="section">
        <h2>✏️ ویرایش شناسه‌ها و ترجمه‌ها</h2>
        <div class="editor-layout">
          <!-- Left: Identifier List -->
          <div>
            <input
              type="text"
              class="search-box"
              id="idSearch"
              placeholder="🔍 جستجوی شناسه..."
              oninput="renderIdList()"
            />
            <div class="id-list-container" id="idListContainer"></div>
            <button
              class="btn btn-info"
              style="width: 100%; margin-top: 12px"
              onclick="addNewIdentifier()"
            >
              ➕ ایجاد شناسه جدید
            </button>
          </div>

          <!-- Right: Edit Form -->
          <div class="edit-form" id="editForm">
            <p style="opacity: 0.5; text-align: center; padding: 40px">
              یک شناسه را از لیست سمت راست انتخاب کنید تا ترجمه‌های آن را ویرایش
              کنید.
            </p>
          </div>
        </div>
      </div>

      <!-- JSON Output & Actions -->
      <div class="section">
        <h2>📄 خروجی JSON</h2>
        <div class="tabs">
          <div class="tab active" onclick="switchJsonTab('full')" id="tab-full">
            📄 Full JSON
          </div>
        </div>
        <pre class="json-output" id="jsonOutput"></pre>

        <div class="action-buttons">
          <button class="btn btn-success" onclick="downloadJSON()">
            ⬇️ دانلود JSON
          </button>
          <button class="btn btn-warning" onclick="copyJSON()">
            📋 کپی JSON
          </button>
          <button class="btn btn-danger" onclick="resetToDefault()">
            🔄 بازنشانی به پیش‌فرض
          </button>
        </div>
      </div>
    </div>

    <script>
      // ============ Default Data ============
      const DEFAULT_RESOURCES = [
        {
          name: "persian",
          locale: "fa-IR",
          language: "fa",
          direction: "rtl",
          resources: [
            { id: "app_name", value: "ساحر علم" },
            { id: "company", value: "فن آوران ساحر علم" },
            { id: "id", value: "شناسه" },
            { id: "file_name", value: "نام فایل" },
            { id: "error", value: "خطا" },
            { id: "required", value: "الزامی است." },
            { id: "loading", value: "بارگزاری" },
            { id: "username", value: "نام کاربری" },
            { id: "password", value: "کلمه عبور" },
            { id: "first_name", value: "نام" },
            { id: "last_name", value: "نام خانوادگی" },
            { id: "verified", value: "تایید شده" },
            { id: "unverified", value: "تایید نشده" },
            { id: "thumbnail", value: "پیش نمایش" },
            { id: "language", value: "زبان" },
            { id: "take_screenshot", value: "تصویر برداری ازمحیط" },
            { id: "enable_capture_mode", value: "فعالسازی حالت تصویر برداری" },
            {
              id: "disable_capture_mode",
              value: "غیرفعالسازی حالت تصویر برداری",
            },
            { id: "be", value: "باشد" },
            { id: "not_to_be", value: "نباشد" },
            { id: "enabled", value: "فعال" },
            { id: "disabled", value: "غیر فعال" },
            { id: "banned", value: "مسدود" },
            { id: "un_banned", value: "غیر مسدود" },
            { id: "audio", value: "صوت" },
            { id: "video", value: "فیلم" },
            { id: "cover_image", value: "تصویر" },
            { id: "image", value: "تصویر" },
            { id: "avatar", value: "شکلک" },
            { id: "unknown", value: "ناشناس" },
            { id: "location", value: "موقعیت جغرافیای" },
            { id: "are_you_sure", value: "اطمینان دارید ؟" },
            { id: "success_msg", value: "عملیات با موفقیت انجام شد" },
            {
              id: "locked_msg",
              value:
                "برای ترک این صفحه باید فعالیت جاری را لغو کرده و یا به اتمام برسانید",
            },
            { id: "empty_content", value: "خالی است" },
            { id: "no_item", value: "محتوایی ثبت نشده است" },
            { id: "drag_drop_here", value: "محل رها کردن فایل" },
            { id: "map_dialog_title", value: "نمایش موقعیت مکانی" },
            { id: "map_dialog_select_title", value: "انتخاب موقعیت جغرافیایی" },
            { id: "color_picker_dialog_title", value: "انتخاب رنگ" },
            { id: "dismiss", value: "رد کردن" },
            { id: "accept", value: "می پذیرم" },
            { id: "ok", value: "بسیار خب" },
            { id: "save", value: "ذخیره" },
            { id: "cancel", value: "لغو" },
            { id: "retry", value: "سعی مجدد" },
            { id: "edit", value: "ویرایش" },
            { id: "reset", value: "باز نشاندن" },
            { id: "change", value: "تغییر" },
            { id: "start", value: "آغاز" },
            { id: "yes", value: "بله" },
            { id: "no", value: "خیر" },
            { id: "verify", value: "تایید کردن" },
            { id: "confirm", value: "تاییدیه" },
            { id: "crop", value: "برش" },
            { id: "clear", value: "پاک کردن" },
            { id: "set", value: "تنظیم" },
            { id: "select", value: "انتخاب" },
            { id: "send", value: "ارسال" },
            { id: "show", value: "نمایش دادن" },
            { id: "hide", value: "پنهان کردن" },
            { id: "search", value: "جستجو" },
            { id: "delete", value: "حذف" },
            { id: "clone", value: "نمونه گیری" },
            { id: "select_all", value: "انتخاب همه" },
            { id: "select_file", value: "انتخاب فایل(ها)" },
            { id: "select_all_group", value: "انتخاب همه موارد فیلتر شده" },
            { id: "deselect_all", value: "لغو همه انتخاب ها" },
            {
              id: "deselect_all_group",
              value: "لغو انتخاب همه موارد فیلتر شده",
            },
            { id: "inverse_selection", value: "انتخاب معکوس" },
            { id: "selected_location", value: "انتخاب محل" },
            { id: "preset_colors", value: "رنگ های پیش فرض" },
            { id: "copy_to_clipboard", value: "ثبت در حافظه موقت" },
            { id: "color", value: "رنگ" },
            { id: "colors", value: "رنگها" },
            { id: "upload", value: "بارگزاری" },
            { id: "import", value: "وارد کردن" },
            { id: "continue", value: "ادامه" },
            { id: "finish", value: "پایان" },
            { id: "enable", value: "فعال" },
            { id: "disable", value: "غیر فعال" },
            { id: "all", value: "همه" },
            { id: "new", value: "جدید" },
            { id: "add", value: "افزودن" },
            { id: "update", value: "بروز رسانی" },
            { id: "details", value: "جزئیات" },
            { id: "translation", value: "ترجمه" },
            { id: "download", value: "دریافت" },
            { id: "list", value: "فهرست" },
            { id: "options", value: "انتخاب ها" },
            { id: "descriptions", value: "توضیحات" },
            { id: "type", value: "نوع" },
            { id: "media", value: "رسانه" },
            { id: "medias", value: "رسانه ها" },
            { id: "summary", value: "خلاصه" },
            { id: "go", value: "رفتن" },
            { id: "input", value: "ورود" },
            { id: "preview", value: "پیش نمایش" },
            { id: "close", value: "بستن" },
            { id: "undo", value: "لغو کردن" },
            { id: "redo", value: "انجام دوباره" },
            { id: "refresh", value: "بازیابی" },
            { id: "submit", value: "ثبت" },
            { id: "first", value: "نخستین" },
            { id: "last", value: "آخرین" },
            { id: "previous", value: "قبلی" },
            { id: "next", value: "بعدی" },
            { id: "back", value: "بازگشت" },
            { id: "x_markdown_preview", value: "پیش نمایش" },
            { id: "x_markdown_image", value: "تصویر" },
            { id: "x_markdown_link", value: "پیوند" },
            { id: "x_markdown_ol", value: "فهرست ترتیبی" },
            { id: "x_markdown_ul", value: "فهرست بدون ترتیب" },
            { id: "x_markdown_quote", value: "نقل قول" },
            { id: "x_markdown_code", value: "کد منبع" },
            { id: "x_markdown_heading", value: "عنوان" },
            { id: "x_markdown_italic", value: "ایتالیک" },
            { id: "x_markdown_bold", value: "پر رنگ" },
            { id: "x_markdown_guide", value: "راهنمای نگارش" },
            { id: "theme", value: "پوسته" },
            { id: "theme_mode", value: "حالت نمایشی" },
            { id: "theme_dark", value: "تیره" },
            { id: "theme_light", value: "روشن" },
            { id: "theme_name", value: "نام پوسته" },
            { id: "theme_name_description", value: "نام پوسته را وارد کنید" },
            { id: "theme_clone_title", value: "نمونه گیری از پوسته" },
            { id: "input_color_title", value: "تعیین رنگ" },
            { id: "input_color_description", value: "مقدار رنگ را وارد کنید" },
            { id: "ide_mode", value: "نوع کد" },
            { id: "ide_format", value: "حالت دهی کد" },
            { id: "print", value: "چاپ" },
            { id: "export_png", value: "خروجی PNG" },
            { id: "export_svg", value: "خروجی SVG" },
            { id: "export_csv", value: "خروجی CSV" },
            { id: "zoom_in", value: "بزرگنمایی" },
            { id: "zoom_out", value: "کوچک نمایی" },
            { id: "zoom_selection", value: "بزرگنمایی ناحیه" },
            { id: "x_axis", value: "محور افقی" },
            { id: "y_axis", value: "محور عمودی" },
            { id: "xy_axis", value: "محورهای عمودی و افقی" },
            { id: "pan", value: "حرکت آزاد" },
            { id: "full_screen", value: "حالت تمام صفحه" },
            { id: "ex_100", value: "داده نا معتبر" },
            { id: "ex_101", value: "تاریخ نامعتبر" },
            { id: "ex_102", value: "درخواست اشتباه" },
            { id: "ex_103", value: "ورودی نامعتبر" },
            { id: "ex_104", value: "پیام نامعتبر" },
            { id: "ex_105", value: "نشانه نامعتبر" },
            { id: "ex_106", value: "کد اععتبارسنجی نامعتبر" },
            { id: "ex_107", value: "پست الکترونیک نامعتبر" },
            { id: "ex_108", value: "شماره تلفن همراه نامعتبر" },
            { id: "ex_109", value: "کلمه عبور نامعتبر" },
            { id: "ex_110", value: "آدرس اینترنتی نامعتبر" },
            { id: "ex_111", value: "دستگاه نامعتبر" },
            { id: "ex_112", value: "تصویر پروفایل نامعتبر" },
            { id: "ex_113", value: "فایل نامعتبر" },
            { id: "ex_114", value: "نام کاربری نامعتبر" },
            { id: "ex_200", value: "یافت نشد" },
            { id: "ex_201", value: "عدم تایید جواز" },
            { id: "ex_202", value: "سطح دسترسی نامعتبر" },
            { id: "ex_203", value: "غیر قابل نمایش" },
            { id: "ex_300", value: "حساب کاربری قفل شده است" },
            { id: "ex_301", value: "خطا در ورود" },
            { id: "ex_302", value: "کاربر قبلا ثبت نام کرده است" },
            { id: "ex_303", value: "کاربر غیرفعال است" },
            { id: "ex_304", value: "دستگاه شما قفل شده است تا : (0)" },
            {
              id: "ex_305",
              value: "امکان درخواست کد اعتبارسنجی وجود ندارد تا : (0)",
            },
            { id: "ex_306", value: "کلمه عبورها یکسان هستند" },
            { id: "ex_307", value: "شماره های تلفن یکسان هستند" },
            { id: "ex_308", value: "آدرس های پست الکترونیک یکسان هستند" },
            { id: "ex_309", value: "شماره تلفن قبلا ثبت شده است" },
            { id: "ex_310", value: "پست الکترونیک قبلا ثبت شده است" },
            { id: "ex_311", value: "تلفن همراه تایید نشده است" },
            { id: "ex_312", value: "پست الکترونیک تایید نشده است" },
            { id: "ex_313", value: "مطلب ممنوعه" },
            { id: "ex_314", value: "کاربر ممنوعه" },
            { id: "ex_400", value: "خطای نا شناخته" },
            { id: "ex_401", value: "مورد تکراری" },
            { id: "ex_402", value: "شکست عملیات" },
            { id: "ex_403", value: "تاریخ مصرف منقضی شده است" },
            { id: "ex_404", value: "غیر قابل اسفاده" },
            { id: "ex_405", value: "تعداد برچسب ها بیش از جد مجاز است" },
            { id: "ex_406", value: "اتمام مهلت زمانی" },
            { id: "ex_500", value: "پرونده خالی است" },
            { id: "ex_501", value: "اندازه بیش از حد مجاز" },
            { id: "ex_502", value: "گونه نامعتبر فایل" },
            { id: "ex_503", value: "تعداد رسانه ها بیش از حد مجاز است" },
            { id: "ex_600", value: "پیکربندی اشتباه" },
            { id: "ex_601", value: "خطا در پیکربندی سامانه ذخیره سازی" },
            { id: "ex_602", value: "خطا در پیکربندی سامانه ذخیره سازی" },
            { id: "ex_900", value: "تعداد فایل ها بیش از حد مجاز است" },
            { id: "ex_901", value: "مجموع اندازه فایل ها کمتر از حد مجاز است" },
            { id: "ex_902", value: "مجموع اندازه فایل ها بیش از حد مجاز است" },
            { id: "ex_903", value: "شرایط و ضوابط باید پذیرفته شوند" },
            { id: "ex_904", value: "عدم پشتیبانی از سیستم موقعیت یاب" },
            {
              id: "ex_905",
              value: "مجوز دسترسی به موقعیت خود را صادر نکرده اید",
            },
            {
              id: "ex_906",
              value: "دسترسی به موقعیت جغرافیایی امکان پذیر نیست",
            },
            { id: "ex_907", value: "عدم پشتیبانی از سیستم ذخیره سازی" },
            {
              id: "ex_908",
              value: "برای دسترسی به محتوا باید از سامانه خارج شوید",
            },
            { id: "ex_909", value: "زبان انتخاب شده نامعتبر است" },
            { id: "ex_910", value: "داده نامعتبر برای انتخاب کاربر" },
          ],
        },
        {
          name: "english",
          locale: "en-US",
          language: "en",
          direction: "ltr",
          resources: [
            { id: "app_name", value: "SaherElm" },
            { id: "company", value: "SaherElm IT Center" },
            { id: "id", value: "ID" },
            { id: "file_name", value: "File Name" },
            { id: "error", value: "Error" },
            { id: "required", value: "required" },
            { id: "loading", value: "loading" },
            { id: "username", value: "Username" },
            { id: "password", value: "Password" },
            { id: "first_name", value: "Firstname" },
            { id: "last_name", value: "Lastname" },
            { id: "verified", value: "Verified" },
            { id: "unverified", value: "Unverified" },
            { id: "thumbnail", value: "Thumbnail" },
            { id: "language", value: "Language" },
            { id: "take_screenshot", value: "Take Screenshot" },
            { id: "enable_capture_mode", value: "Enable Capture Mode" },
            { id: "disable_capture_mode", value: "Disable Capture Mode" },
            { id: "be", value: "Be" },
            { id: "not_to_be", value: "Not to be" },
            { id: "enabled", value: "Enabled" },
            { id: "disabled", value: "Disabled" },
            { id: "banned", value: "Banned" },
            { id: "un_banned", value: "UnBanned" },
            { id: "audio", value: "Audio" },
            { id: "video", value: "Video" },
            { id: "cover_image", value: "Cover Image" },
            { id: "image", value: "Image" },
            { id: "avatar", value: "Avatar" },
            { id: "unknown", value: "Unknown" },
            { id: "location", value: "Location" },
            { id: "are_you_sure", value: "Are You Sure ?" },
            { id: "success_msg", value: "Operation done Successfully ..." },
            {
              id: "locked_msg",
              value: "You have to finish/cancel current proccess for leaving",
            },
            { id: "empty_content", value: "Empty" },
            { id: "no_item", value: "there is no Content" },
            { id: "drag_drop_here", value: "Drop File Here" },
            { id: "map_dialog_title", value: "Show Location" },
            { id: "map_dialog_select_title", value: "Select Location" },
            { id: "color_picker_dialog_title", value: "Select Color" },
            { id: "dismiss", value: "dismiss" },
            { id: "accept", value: "Accept" },
            { id: "ok", value: "Ok" },
            { id: "save", value: "Save" },
            { id: "cancel", value: "Cancel" },
            { id: "retry", value: "Retry" },
            { id: "edit", value: "Edit" },
            { id: "reset", value: "Reset" },
            { id: "change", value: "Change" },
            { id: "start", value: "Start" },
            { id: "yes", value: "Yes" },
            { id: "no", value: "No" },
            { id: "verify", value: "Verify" },
            { id: "confirm", value: "Confirm" },
            { id: "crop", value: "Crop" },
            { id: "clear", value: "Clear" },
            { id: "set", value: "Set" },
            { id: "select", value: "Select" },
            { id: "send", value: "Send" },
            { id: "show", value: "Show" },
            { id: "hide", value: "Hide" },
            { id: "search", value: "Search" },
            { id: "delete", value: "Delete" },
            { id: "clone", value: "Clone" },
            { id: "select_all", value: "Select All" },
            { id: "select_file", value: "Select File(s)" },
            { id: "select_all_group", value: "Select All Filtered Items" },
            { id: "deselect_all", value: "Deselect All" },
            { id: "deselect_all_group", value: "Deselect All Filtered Items" },
            { id: "inverse_selection", value: "Inverse Selection" },
            { id: "selected_location", value: "Selected Location" },
            { id: "preset_colors", value: "Preset Colors" },
            { id: "copy_to_clipboard", value: "Copy to Clipboard" },
            { id: "color", value: "Color" },
            { id: "colors", value: "Colors" },
            { id: "upload", value: "Upload" },
            { id: "import", value: "Import" },
            { id: "continue", value: "Continue" },
            { id: "finish", value: "Finish" },
            { id: "enable", value: "Enable" },
            { id: "disable", value: "Disable" },
            { id: "all", value: "All" },
            { id: "new", value: "New" },
            { id: "add", value: "Add" },
            { id: "update", value: "Update" },
            { id: "details", value: "Details" },
            { id: "translation", value: "Translation" },
            { id: "download", value: "Download" },
            { id: "list", value: "List" },
            { id: "options", value: "Options" },
            { id: "descriptions", value: "Description" },
            { id: "type", value: "Type" },
            { id: "media", value: "Media" },
            { id: "medias", value: "Medias" },
            { id: "summary", value: "Summary" },
            { id: "go", value: "Go" },
            { id: "input", value: "Input" },
            { id: "preview", value: "Preview" },
            { id: "close", value: "Close" },
            { id: "undo", value: "Undo" },
            { id: "redo", value: "Redo" },
            { id: "refresh", value: "Refresh" },
            { id: "submit", value: "Submit" },
            { id: "first", value: "First" },
            { id: "last", value: "Last" },
            { id: "previous", value: "Previous" },
            { id: "next", value: "Next" },
            { id: "back", value: "Back" },
            { id: "x_markdown_preview", value: "Preview" },
            { id: "x_markdown_image", value: "Image" },
            { id: "x_markdown_link", value: "Link" },
            { id: "x_markdown_ol", value: "Ordered List" },
            { id: "x_markdown_ul", value: "Unordered List" },
            { id: "x_markdown_quote", value: "Quote" },
            { id: "x_markdown_code", value: "Code" },
            { id: "x_markdown_heading", value: "Heading" },
            { id: "x_markdown_italic", value: "Italic" },
            { id: "x_markdown_bold", value: "Bold" },
            { id: "x_markdown_guide", value: "Markdown Guide" },
            { id: "theme", value: "Theme" },
            { id: "theme_mode", value: "Theme Mode" },
            { id: "theme_dark", value: "Dark" },
            { id: "theme_light", value: "Light" },
            { id: "theme_name", value: "Theme Name" },
            { id: "theme_name_description", value: "Enter Theme name" },
            { id: "theme_clone_title", value: "Theme Cloning" },
            { id: "input_color_title", value: "Color Value" },
            { id: "input_color_description", value: "input color value" },
            { id: "ide_mode", value: "Code Style" },
            { id: "ide_format", value: "Code Formatting" },
            { id: "print", value: "Print" },
            { id: "export_png", value: "Export PNG" },
            { id: "export_svg", value: "Export SVG" },
            { id: "export_csv", value: "Export CSV" },
            { id: "zoom_in", value: "Zoom In" },
            { id: "zoom_out", value: "Zoom Out" },
            { id: "zoom_selection", value: "Zoom Selection" },
            { id: "x_axis", value: "X Axis" },
            { id: "y_axis", value: "Y Axis" },
            { id: "xy_axis", value: "XY Axis" },
            { id: "pan", value: "Pan" },
            { id: "full_screen", value: "Full Screen" },
            { id: "ex_100", value: "Invalid Data ..." },
            { id: "ex_101", value: "Invalid Date ..." },
            { id: "ex_102", value: "Bad Request ..." },
            { id: "ex_103", value: "Invalid Args ..." },
            { id: "ex_104", value: "Invalid Message ..." },
            { id: "ex_105", value: "Invalid Token ..." },
            { id: "ex_106", value: "Invalid Verification Code ..." },
            { id: "ex_107", value: "Invalid Email Address ..." },
            { id: "ex_108", value: "Invalid Mobile Number ..." },
            { id: "ex_109", value: "Invalid Password ..." },
            { id: "ex_110", value: "Invalid URL ..." },
            { id: "ex_111", value: "Invalid Device ..." },
            { id: "ex_112", value: "Invalid Profile Image ..." },
            { id: "ex_113", value: "Invalid File ..." },
            { id: "ex_114", value: "Invalid Username ..." },
            { id: "ex_200", value: "Not Found ..." },
            { id: "ex_201", value: "Not Allowed ..." },
            { id: "ex_202", value: "Not Authorized ..." },
            { id: "ex_203", value: "Not Visible ..." },
            { id: "ex_300", value: "Account Locked out ..." },
            { id: "ex_301", value: "Login Failed ..." },
            { id: "ex_302", value: "User Registered Before ..." },
            { id: "ex_303", value: "User Disabled ..." },
            { id: "ex_304", value: "Device Banned Until:(0)" },
            {
              id: "ex_305",
              value: "You can not Request Verification code Until:(0)",
            },
            { id: "ex_306", value: "Passwords Same ..." },
            { id: "ex_307", value: "Mobiles Same ..." },
            { id: "ex_308", value: "Emails Same ..." },
            { id: "ex_309", value: "Mobile In Used ..." },
            { id: "ex_310", value: "Email In Used ..." },
            { id: "ex_311", value: "Mobile Not Confirmed ..." },
            { id: "ex_312", value: "Email Not Confirmed ..." },
            { id: "ex_313", value: "Post Banned ..." },
            { id: "ex_314", value: "User Banned ..." },
            { id: "ex_400", value: "Unknown Error ..." },
            { id: "ex_401", value: "Duplicate ..." },
            { id: "ex_402", value: "Action Failed ..." },
            { id: "ex_403", value: "Expired Date ..." },
            { id: "ex_404", value: "Unavailable ..." },
            { id: "ex_405", value: "Max Allowed Tag Exceeded ..." },
            { id: "ex_406", value: "Timeout ..." },
            { id: "ex_500", value: "Empty File ..." },
            { id: "ex_501", value: "max File size exceeded ..." },
            { id: "ex_502", value: "Unsupported File Type ..." },
            { id: "ex_503", value: "Max Allowed Media Exceeded ..." },
            { id: "ex_600", value: "Invalid Configuration ..." },
            {
              id: "ex_601",
              value: "Storage Service Initiallization Failed ...",
            },
            {
              id: "ex_602",
              value: "Message Service Initialization Failed ...",
            },
            { id: "ex_900", value: "Max Allowed Files Exceeded ..." },
            { id: "ex_901", value: "Min Files Size Exceeded ..." },
            { id: "ex_902", value: "Max Files Size Exceeded ..." },
            { id: "ex_903", value: "Must Accept Terms And Conditions ..." },
            {
              id: "ex_904",
              value: "Browser does not support Location Services ...",
            },
            {
              id: "ex_905",
              value: "You have rejected access to your Location ...",
            },
            { id: "ex_906", value: "Unable to determine your Location ..." },
            {
              id: "ex_907",
              value: "Browser does not support Storage Services ...",
            },
            { id: "ex_908", value: "Must Logout to access content ..." },
            { id: "ex_909", value: "Invalid Locale ..." },
            { id: "ex_910", value: "Invalid User Selector ..." },
          ],
        },
      ];

      // ============ State ============
      let resourcesData = JSON.parse(JSON.stringify(DEFAULT_RESOURCES));
      let selectedId = null;
      let jsonTab = "full";

      // ============ Initialization ============
      function init() {
        renderLanguages();
        renderIdList();
        renderJson();
      }

      // ============ Language Management ============
      function renderLanguages() {
        const container = document.getElementById("langList");
        container.innerHTML = "";
        resourcesData.forEach((lang, index) => {
          const chip = document.createElement("div");
          chip.className = "lang-chip";
          chip.innerHTML = `
            <strong>${lang.name}</strong> (${lang.locale})
            ${resourcesData.length > 1 ? `<span class="remove-lang" onclick="removeLanguage(${index})" title="حذف زبان">✕</span>` : ""}
          `;
          container.appendChild(chip);
        });
      }

      function addNewLanguage() {
        const name = document.getElementById("newLangName").value.trim();
        const locale = document.getElementById("newLangLocale").value.trim();
        const code = document.getElementById("newLangCode").value.trim();
        const dir = document.getElementById("newLangDir").value;

        if (!name || !locale || !code) {
          alert("لطفاً تمام فیلدهای زبان جدید را پر کنید.");
          return;
        }

        if (resourcesData.find((l) => l.name === name)) {
          alert("زبانی با این نام از قبل وجود دارد.");
          return;
        }

        // Create empty resources for all existing IDs
        const allIds = getAllIdentifiers();
        const newResources = allIds.map((id) => ({ id, value: "" }));

        resourcesData.push({
          name,
          locale,
          language: code,
          direction: dir,
          resources: newResources,
        });

        document.getElementById("newLangName").value = "";
        document.getElementById("newLangLocale").value = "";
        document.getElementById("newLangCode").value = "";

        renderLanguages();
        renderIdList();
        renderJson();
        alert(`زبان ${name} با موفقیت اضافه شد.`);
      }

      function removeLanguage(index) {
        if (
          confirm(`آیا از حذف زبان ${resourcesData[index].name} اطمینان دارید؟`)
        ) {
          resourcesData.splice(index, 1);
          renderLanguages();
          renderIdList();
          renderJson();
          if (selectedId) selectIdentifier(selectedId);
        }
      }

      // ============ Identifier Management ============
      function getAllIdentifiers() {
        const ids = new Set();
        resourcesData.forEach((lang) => {
          lang.resources.forEach((res) => ids.add(res.id));
        });
        return Array.from(ids).sort();
      }

      function renderIdList() {
        const container = document.getElementById("idListContainer");
        const search = document.getElementById("idSearch").value.toLowerCase();
        container.innerHTML = "";

        const allIds = getAllIdentifiers();
        const filteredIds = allIds.filter((id) =>
          id.toLowerCase().includes(search),
        );

        filteredIds.forEach((id) => {
          const item = document.createElement("div");
          item.className = `id-item ${selectedId === id ? "active" : ""}`;

          // Check for missing or empty translations
          let hasMissing = false;
          resourcesData.forEach((lang) => {
            const res = lang.resources.find((r) => r.id === id);
            if (!res || !res.value.trim()) {
              hasMissing = true;
            }
          });

          item.innerHTML = `
            <span>${id}</span>
            ${hasMissing ? '<span class="warning-badge" title="فاقد ترجمه در یک یا چند زبان">⚠️</span>' : ""}
          `;
          item.onclick = () => selectIdentifier(id);
          container.appendChild(item);
        });
      }

      function selectIdentifier(id) {
        selectedId = id;
        renderIdList(); // to update active state

        const formContainer = document.getElementById("editForm");
        formContainer.innerHTML = `<h3 style="margin-bottom: 16px; color: #7ec8ff;">ویرایش شناسه: <span style="font-family: monospace; color: #fff;">${id}</span></h3>`;

        resourcesData.forEach((lang, langIndex) => {
          const res = lang.resources.find((r) => r.id === id);
          const currentValue = res ? res.value : "";

          const row = document.createElement("div");
          row.className = "edit-row";
          row.innerHTML = `
            <label>${lang.name} (${lang.locale}) ${!res ? '<span style="color:#f44336">(جدید - فاقد مقدار)</span>' : ""}</label>
            <textarea 
              data-lang-index="${langIndex}" 
              data-res-id="${id}"
              oninput="updateTranslation(this)"
              placeholder="ترجمه را وارد کنید..."
            >${currentValue}</textarea>
          `;
          formContainer.appendChild(row);
        });

        const addBtn = document.createElement("button");
        addBtn.className = "btn btn-primary";
        addBtn.style.width = "100%";
        addBtn.style.marginTop = "12px";
        addBtn.textContent = "💾 ذخیره تغییرات و به‌روزرسانی JSON";
        addBtn.onclick = () => {
          renderJson();
          renderIdList(); // update warning badges
          alert("تغییرات با موفقیت ذخیره شد.");
        };
        formContainer.appendChild(addBtn);
      }

      function updateTranslation(textarea) {
        const langIndex = parseInt(textarea.dataset.langIndex);
        const id = textarea.dataset.resId;
        const newValue = textarea.value;

        const lang = resourcesData[langIndex];
        const resIndex = lang.resources.findIndex((r) => r.id === id);

        if (resIndex !== -1) {
          lang.resources[resIndex].value = newValue;
        } else {
          lang.resources.push({ id, value: newValue });
        }
      }

      function addNewIdentifier() {
        const newId = prompt("شناسه جدید را وارد کنید (مثال: my_new_key):");
        if (!newId || !newId.trim()) return;

        const trimmedId = newId.trim();
        if (getAllIdentifiers().includes(trimmedId)) {
          alert("این شناسه از قبل وجود دارد.");
          return;
        }

        // Add to all languages with empty value
        resourcesData.forEach((lang) => {
          lang.resources.push({ id: trimmedId, value: "" });
        });

        renderIdList();
        renderJson();
        selectIdentifier(trimmedId);
      }

      // ============ JSON & Actions ============
      function switchJsonTab(tab) {
        jsonTab = tab;
        document
          .querySelectorAll(".tab")
          .forEach((t) => t.classList.remove("active"));
        document.getElementById(`tab-${tab}`).classList.add("active");
        renderJson();
      }

      function renderJson() {
        const output = document.getElementById("jsonOutput");
        if (jsonTab === "full") {
          output.textContent = JSON.stringify(resourcesData, null, 2);
        }
      }

      function downloadJSON() {
        const blob = new Blob([JSON.stringify(resourcesData, null, 2)], {
          type: "application/json",
        });
        const url = URL.createObjectURL(blob);
        const a = document.createElement("a");
        a.href = url;
        a.download = "x-framework-translations.json";
        a.click();
        URL.revokeObjectURL(url);
      }

      function copyJSON() {
        navigator.clipboard
          .writeText(JSON.stringify(resourcesData, null, 2))
          .then(() => alert("✅ JSON در کلیپ‌بورد کپی شد."))
          .catch(() => alert("❌ خطا در کپی کردن."));
      }

      function resetToDefault() {
        if (
          confirm(
            "آیا اطمینان دارید؟ تمام تغییرات شما از بین خواهد رفت و به حالت پیش‌فرض بازمی‌گردد.",
          )
        ) {
          resourcesData = JSON.parse(JSON.stringify(DEFAULT_RESOURCES));
          selectedId = null;
          document.getElementById("editForm").innerHTML =
            '<p style="opacity: 0.5; text-align: center; padding: 40px;">یک شناسه را از لیست سمت راست انتخاب کنید تا ترجمه‌های آن را ویرایش کنید.</p>';
          renderLanguages();
          renderIdList();
          renderJson();
        }
      }

      // ============ File Upload ============
      function handleFileUpload(event) {
        const file = event.target.files[0];
        if (!file) return;

        const reader = new FileReader();
        reader.onload = function (e) {
          try {
            const parsed = JSON.parse(e.target.result);
            if (!Array.isArray(parsed)) {
              throw new Error("فایل JSON باید یک آرایه از اشیاء زبان باشد.");
            }

            // Basic validation
            parsed.forEach((lang) => {
              if (
                !lang.name ||
                !lang.locale ||
                !Array.isArray(lang.resources)
              ) {
                throw new Error(
                  "ساختار هر زبان باید شامل name، locale و resources باشد.",
                );
              }
            });

            resourcesData = parsed;
            selectedId = null;
            document.getElementById("editForm").innerHTML =
              '<p style="opacity: 0.5; text-align: center; padding: 40px;">یک شناسه را از لیست سمت راست انتخاب کنید تا ترجمه‌های آن را ویرایش کنید.</p>';

            document.getElementById("uploadStatus").className =
              "upload-status success";
            document.getElementById("uploadStatus").textContent =
              `✅ فایل با موفقیت بارگذاری شد. (${resourcesData.length} زبان)`;

            renderLanguages();
            renderIdList();
            renderJson();
          } catch (err) {
            document.getElementById("uploadStatus").className =
              "upload-status error";
            document.getElementById("uploadStatus").textContent =
              `❌ خطا: ${err.message}`;
          }
        };
        reader.readAsText(file);
      }

      function switchSource(source) {
        document
          .querySelectorAll(".source-tab")
          .forEach((t) => t.classList.remove("active"));
        event.target.classList.add("active");
        document
          .querySelectorAll(".source-content")
          .forEach((c) => c.classList.remove("active"));
        document.getElementById(`source-${source}`).classList.add("active");
      }

      // Start
      init();
    </script>
  </body>
</html>

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\IXOwnedItemResourceProvider.cs

using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using xModels.Dtos;
using xStringService.Models.Dtos;

namespace xStringService.Interfaces
{
    public interface IXOwnedItemResourceProvider
    {
        //
        #region Group Actions ...
        /// <summary>
        /// Retrieve a List of Exists Languages for Current Item ...
        /// </summary>
        /// <param name="owner"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<IEnumerable<string>> GetLanguages(
            object owner,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Retrieve all Exists Items of type ...
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<IEnumerable<XStringDto>> GetResourceLocales(
            object owner,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Remove all Exists Locales which Related to this Provider ...
        /// </summary>
        /// <param name="connectionId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task RemoveResourceLocales(
            object owner,
            string connectionId = null,
            CancellationToken cancellationToken = default
        );
        #endregion

        //
        #region Item Actions ...
        /// <summary>
        /// Get all Exists Items for Specified identitifer ...
        /// </summary>
        /// <param name="identifier"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<IEnumerable<XStringDto>> GetResourceLocales(
            object owner,
            object identifier,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Get all Exists Items for Specified identitifer ...
        /// </summary>
        /// <param name="identifier"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<IEnumerable<XLocaleResourceDto>> GetLocales(
            object owner,
            object identifier,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Get all Exists Locales Representaion based on Specified Identitifer
        /// as XResourceDto ...
        /// </summary>
        /// <param name="identifier"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XResourceDto> GetResource(
            object owner,
            object identifier,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Add Or Update Locale for Specified Identitifer ...
        /// </summary>
        /// <param name="identifier"></param>
        /// <param name="locale"></param>
        /// <param name="connectionId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XResourceDto> AddOrUpdateLocale(
            object owner,
            object identifier,
            XLocaleResourceDto locale,
            string connectionId = null,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Update a Resource State ...
        /// </summary>
        /// <param name="identifier"></param>
        /// <param name="resource"></param>
        /// <param name="connectionId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XResourceDto> AddOrUpdateResource(
            object owner,
            object identifier,
            XResourceDto resource,
            string connectionId = null,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Remove Specified Language Locale of Specified Identifier ...
        /// </summary>
        /// <param name="identifier"></param>
        /// <param name="language"></param>
        /// <param name="connectionId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XResourceDto> RemoveLocale(
            object owner,
            object identifier,
            string language,
            string connectionId = null,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Remove all Exists Locales for Specified Identifier ...
        /// </summary>
        /// <param name="identifier"></param>
        /// <param name="connectionId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task RemoveLocales(
            object owner,
            object identifier,
            string connectionId = null,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Check Specified ocale Exists for Specified Identifier ...
        /// </summary>
        /// <param name="identifier"></param>
        /// <param name="language"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<bool> HasLocale(
            object owner,
            object identifier,
            string language,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Get all Exists Languages for Specified Identifier ...
        /// </summary>
        /// <param name="identifier"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<IEnumerable<string>> GetLanguages(
            object owner,
            object identifier,
            CancellationToken cancellationToken = default
        );
        #endregion

        //
        #region Resource Title Action ...
        /// <summary>
        /// Retrieve Resource Title ...
        /// </summary>
        /// <param name="identifier"></param>
        /// <returns></returns>
        string GetResourceTitle(object identifier = null);
        #endregion        
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\XBaseOwnedItemResourceProvider.cs

using xStringService.Interfaces;

namespace xStringService.Providers
{
    /// <summary>
    /// Owned Item Resource Provider ...
    /// </summary>
    public abstract class XBaseOwnedItemResourceProvider : IXOwnedItemResourceProvider
    {
        
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Configurations\XAiApiConfiguration.cs

using xAiModels.Constants;
using xAiModels.Models;

namespace xAiModels.Configurations
{
    public class XAiApiConfiguration
    {
        /// <summary>
        /// Model Providers ...
        /// </summary>
        public XAiModelDescriptor[] Models { get; set; } = [];

        /// <summary>
        /// Each User must Has a Default Project, this is Title Resource Identifier ... 
        /// </summary>
        public string DefaultProjectTitleResource { get; set; } = XAiModelsConstants.DefaultXAiProjectTitleResourceID;

        /// <summary>
        /// Each User must Has a Default Project, this is Description Resource Identifier ... 
        /// </summary>
        public string DefaultProjectDescriptionResource { get; set; } = XAiModelsConstants.DefaultXAiProjectDescriptionResourceID;

        /// <summary>
        /// Each User must Has a Default Conversation, this is Title Resource Identifier ...  ...
        /// </summary>
        public string DefaultConversationTitleResource { get; set; } = XAiModelsConstants.DefaultXAiConversationTitleResourceID;
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Configurations\XAiModelMappersProfile.cs

using AutoMapper;
using xAiModels.Models.Dtos;
using xAiModels.Models.Entities;

namespace xAiModels.Configurations
{
    public class XAiModelMappersProfile : Profile
    {
        public XAiModelMappersProfile()
        {
            //
            // XAiMessage ...
            CreateMap<XAiMessage, XAiMessageDto>();

            //
            // XAiProject ...
            CreateMap<XAiProject, XAiProjectDto>();

            //
            // XAiConversation ...
            CreateMap<XAiConversation, XAiConversationDto>();
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Configurations\Entities\XAiConversationEntityConfiguration.cs

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using xAiModels.Constants;
using xAiModels.Models.Entities;
using xDataService.Providers;

namespace xAiModels.Configurations.Entities
{
    public class XAiConversationEntityConfiguration : XBaseEntityTypeConfiguration<XAiConversation, Guid>
    {
        public override void Configure(EntityTypeBuilder<XAiConversation> builder)
        {
            builder.ToTable(XAiModelsConstants.XAiConversationTable);
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Configurations\Entities\XAiMessageEntityConfiguration.cs

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using xAiModels.Constants;
using xAiModels.Models.Entities;
using xDataService.Providers;

namespace xAiModels.Configurations.Entities
{
    public class XAiMessageEntityConfiguration : XBaseEntityTypeConfiguration<XAiMessage, Guid>
    {
        public override void Configure(EntityTypeBuilder<XAiMessage> builder)
        {
            builder.ToTable(XAiModelsConstants.XAiMessageTable);
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Configurations\Entities\XAiProjectEntityConfiguration.cs

using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using xAiModels.Constants;
using xAiModels.Models.Entities;
using xDataService.Providers;

namespace xAiModels.Configurations.Entities
{
    public class XAiProjectEntityConfiguration : XBaseEntityTypeConfiguration<XAiProject, Guid>
    {
        public override void Configure(EntityTypeBuilder<XAiProject> builder)
        {
            builder.ToTable(XAiModelsConstants.XAiProjectTable);
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Constants\ConfigurationNodeNames.cs

namespace xAiModels.Constants
{
    public partial struct ConfigurationNodeNames
    {
        public const string AI_API_NODE = "AiApiConfiguration";
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Constants\XAiChatRole.cs

using xExceptions.Attributes;

namespace xAiModels.Constants
{
    public struct XAiChatRoles
    {
        public const string None = "none";
        public const string User = "user";
        public const string Tool = "tool";
        public const string System = "system";
        public const string Assistant = "assistant";
    }

    public enum XAiChatRole
    {
        [StringValue(XAiChatRoles.None)]
        None,
        [StringValue(XAiChatRoles.User)]
        User,
        [StringValue(XAiChatRoles.Tool)]
        Tool,
        [StringValue(XAiChatRoles.System)]
        System,
        [StringValue(XAiChatRoles.Assistant)]
        Assistant,
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Constants\XAiDataProviderConstants.cs

using xExceptions.Attributes;

namespace xAiModels.Constants
{
    public struct XAiDataProviderConstants
    {
        public const string XDefaultAiProjectSuffix = "DefAiPrj";
        public const string XDefaultAiConversationSuffix = "DefAiCnv";
    }

    public enum XAiDataProviderHubActions
    {
        //
        // Project ...
        [StringValue("ProjectCreated")]
        ProjectCreated,

        [StringValue("ProjectUpdated")]
        ProjectUpdated,

        [StringValue("ProjectRemoved")]
        ProjectRemoved,

        //
        // Conversation ...
        [StringValue("ConversationCreated")]
        ConversationCreated,

        [StringValue("ConversationUpdated")]
        ConversationUpdated,

        [StringValue("ConversationRemoved")]
        ConversationRemoved,

        //
        // Message ...
        [StringValue("MessageCreated")]
        MessageCreated,

        [StringValue("MessageUpdated")]
        MessageUpdated,

        [StringValue("MessageRemoved")]
        MessageRemoved,
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Constants\XAiModelProviderType.cs

namespace xAiModels.Constants
{
    /// <summary>
    /// Specifiy Provider Type ...
    /// </summary>
    public enum XAiModelProviderType
    {
        None,
        Ollama,
        DeepSeek,
        HuggingFace        
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Constants\XAiModelsConstants.cs

namespace xAiModels.Constants
{
    public struct XAiModelsConstants
    {
        //
        // Service Extensions Log Tag ...
        public const string XAiModelsDILogTag = "XAiModels";
        
        //
        // Table Mapping Identifiers ...
        public const string XAiMessageTable = "AiMessages";
        public const string XAiProjectTable = "AiProjects";
        public const string XAiConversationTable = "AiConversations";

        //
        // GraphQL Collection Mappings ...
        public const string XAiMessageSingleName = "aiMessage";
        public const string XAiProjectSingleName = "aiProject";
        public const string XAiConversationSignleName = "aiConversation";

        public const string XAiMessageCollectionName = "aiMessages";
        public const string XAiProjectCollectionName = "AiProjectsa";
        public const string XAiConversationCollectionName = "aiConversations";

        //
        // Hubs ...
        public const string XAiMessageDtoHub = "aiMessageDto";
        public const string XAiMessageEntityHub = "aiMessageEntity";

        public const string XAiProjectDtoHub = "aiProjectDto";
        public const string XAiProjectEntityHub = "aiProjectEntity";

        public const string XAiConversationDtoHub = "aiConversationDto";
        public const string XAiConversationEntityHub = "aiConversationEntity";

        //
        // Resource Provider Identifiers ...
        public const string XAiProjectTitleIdentifier = "AiPrjT";
        public const string XAiProjectDescriptionIdentifier = "AiPrjD";
        public const string XAiConversationTitleIdentifier = "AiCnvrT";

        //
        // EF Repositories Helper Extensions ...
        public const string GetXAiMessageEFRepositoryDescriptor = "GetXAiMessageEFRepositoryDescriptor";
        public const string GetXAiProjectEFRepositoryDescriptor = "GetXAiProjectEFRepositoryDescriptor";
        public const string GetXAiConversationEFRepositoryDescriptor = "GetXAiConversationEFRepositoryDescriptor";

        //
        public const string DefaultXAiProjectTitleResourceID = "XAiPrjT";         
        public const string DefaultXAiConversationTitleResourceID = "XAiCnvT";         
        public const string DefaultXAiProjectDescriptionResourceID = "XAiPrjD";         

        //
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\DI\XDIHelperExtension.cs

using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using xAiModels.Providers.Dtos;
using xAiModels.Providers.Entities;
using xCommons.Extensions;
using xDataService.Constants;
using xDataService.Db;
using xDataService.Models;
using xExceptions.Constants;
using xPushService.Helpers;
using xPushService.DI;
using xDataService.DI;
using xAiModels.Interfaces.Dtos;
using xAiModels.Helpers;
using xCommons.Helpers;
using xAiModels.Interfaces.Entities;
using xAiModels.Interfaces;
using xAiModels.Providers;
using xAiModels.Constants;
using xAiModels.Interfaces.Enrichers;
using xAiModels.Providers.Enrichers;

namespace xAiModels.DI
{
    /// <summary>
    /// Service Registration Extensions Classes ...
    /// </summary>
    public static class XDIHelperExtension
    {
        /// <summary>
        /// Register Service ...
        /// </summary>
        /// <param name="services"></param>
        /// <param name="repositoryType"></param>
        /// <param name="lifeTime"></param>
        public static void AddXAiDataService(
            this IServiceCollection services,
            XRepositoryType repositoryType,
            ServiceLifetime lifeTime = ServiceLifetime.Scoped
        )
        {
            AddAiDataService(
                contextType: null,
                services: services,
                lifeTime: lifeTime,
                repositoryType: repositoryType
            );
        }

        /// <summary>
        /// Register Service ...
        /// </summary>
        /// <typeparam name="TContext"></typeparam>
        /// <param name="services"></param>
        /// <param name="repositoryType"></param>
        /// <param name="lifeTime"></param>
        public static void AddXAiDataService<TContext>(
            this IServiceCollection services,
            XRepositoryType repositoryType,
            ServiceLifetime lifeTime = ServiceLifetime.Scoped
        )
        where TContext : XDbContext
        {
            AddAiDataService(
                services: services,
                lifeTime: lifeTime,
                contextType: typeof(TContext),
                repositoryType: repositoryType
            );
        }

        /// <summary>
        /// Use XSubCategoryService Middleware ... 
        /// </summary>
        /// <param name="app"></param>
        /// <param name="isDevelopmentEnvironment"></param>
        public static void UseXAiDataService(
            this IApplicationBuilder app,
            bool isDevelopmentEnvironment = false
        )
        {
            //
            #region Using Hubs ...
            //
            var pushHelper = new XPushServiceHelper();

            //
            // XAiMessage ...
            pushHelper.AddHub<XAiMessageDtoHub>(XAiModelsConstants.XAiMessageDtoHub);
            pushHelper.AddHub<XAiMessageEntityHub>(XAiModelsConstants.XAiMessageEntityHub);

            //
            // XAiProject ...
            pushHelper.AddHub<XAiProjectDtoHub>(XAiModelsConstants.XAiProjectDtoHub);
            pushHelper.AddHub<XAiProjectEntityHub>(XAiModelsConstants.XAiProjectEntityHub);

            //
            // XAiConversation ...
            pushHelper.AddHub<XAiConversationDtoHub>(XAiModelsConstants.XAiConversationDtoHub);
            pushHelper.AddHub<XAiConversationEntityHub>(XAiModelsConstants.XAiConversationEntityHub);

            //
            app.UseXPushService(pushHelper);
            #endregion

            //
            // Using XAiConversation Repository Descriptor ...
            if (!aiConversationDescriptor.IsNull())
            {
                //
                app.UseXRepository(
                    descriptor: aiConversationDescriptor,
                    isDevelopmentEnvironment: isDevelopmentEnvironment
                );
            }

            //
            // Using XAiMessage Repository Descriptor ...
            if (!aiMessageDescriptor.IsNull())
            {
                //
                app.UseXRepository(
                    descriptor: aiMessageDescriptor,
                    isDevelopmentEnvironment: isDevelopmentEnvironment
                );
            }

            //
            // Using XAiProject Repository Descriptor ...
            if (!aiProjectDescriptor.IsNull())
            {
                //
                app.UseXRepository(
                    descriptor: aiProjectDescriptor,
                    isDevelopmentEnvironment: isDevelopmentEnvironment
                );
            }

            //
            // Create an Scope for Services Acces ...
            // Handle Seeding Category/SubCategory Resources ...
            // TODO: Enable Seeding if Required ...
            // using (var scope = app.ApplicationServices.CreateScope())
            // {
            //     //
            //     // Retrieve Configuratuions ...
            //     var aiMessageProvider = scope.ServiceProvider.GetService<IXAiMessageServiceProvider>();
            //     var aiProjectProvider = scope.ServiceProvider.GetService<IXAiProjectServiceProvider>();
            //     var aiConversationProvider = scope.ServiceProvider.GetService<IXAiConversationServiceProvider>();
            //     var configuration = scope.ServiceProvider.GetService<XAiDataServiceServiceConfiguration>();
            //     if (!configuration.IsNullOrDefault() &&
            //         !categoryProvider.IsNullOrDefault() &&
            //         !subaiConversationDescriptor.IsNullOrDefault()
            //     )
            //     {
            //         //
            //         // Logging ...
            //         Log("Start Seeding ...");

            //         //
            //         // Count Data in Category ...
            //         var hasCategory = (categoryProvider.Count().GetAwaiter().GetResult()) > 0;
            //         var hasSubCategory = (subCategoryProvider.Count().GetAwaiter().GetResult()) > 0;

            //         //
            //         // Seed Category Resources ...
            //         var canSeed =
            //             !hasCategory &&
            //             configuration.Categories.HasChild();
            //         if (canSeed)
            //         {
            //             //
            //             // Loop through Provided items ...
            //             foreach (var cat in configuration.Categories)
            //             {
            //                 //
            //                 try
            //                 {
            //                     //
            //                     Log($"Start Seeding Category: {cat.Titles.Locales.ToArray()[0].Value} ...");

            //                     //
            //                     categoryProvider.Refresh(
            //                         resource: cat,
            //                         userInfo: null,
            //                         connectionId: null,
            //                         cancellationToken: default
            //                     )
            //                     .GetAwaiter()
            //                     .GetResult();

            //                     //
            //                     Log($"Seeding Category: {cat.Titles.Locales.ToArray()[0].Value} Finished Successfully ...");
            //                 }
            //                 catch (Exception ex)
            //                 {
            //                     Log($"Seeding Category: {cat.Titles.Locales.ToArray()[0].Value} Failed due: {ex.Message} ...");
            //                 }
            //             }
            //         }

            //         //
            //         // Seed SubCategory Resources ...
            //         canSeed =
            //             !hasSubCategory &&
            //             configuration.SubCategories.HasChild();
            //         if (canSeed)
            //         {
            //             //
            //             // Loop through Provided items ...
            //             foreach (var subcat in configuration.SubCategories)
            //             {
            //                 //
            //                 try
            //                 {
            //                     //
            //                     Log($"Start Seeding SubCategory: {subcat.Titles.Locales.ToArray()[0].Value} ...");

            //                     //
            //                     subCategoryProvider.Refresh(
            //                         userInfo: null,
            //                         resource: subcat,
            //                         connectionId: null,
            //                         cancellationToken: default
            //                     )
            //                     .GetAwaiter()
            //                     .GetResult();

            //                     //
            //                     Log($"Seeding SubCategory: {subcat.Titles.Locales.ToArray()[0].Value} Finished Successfully ...");
            //                 }
            //                 catch (Exception ex)
            //                 {
            //                     Log($"Seeding SubCategory: {subcat.Titles.Locales.ToArray()[0].Value} Failed due: {ex.Message} ...");
            //                 }
            //             }
            //         }

            //         //
            //         Log("Finish Seeding ...");
            //     }
            // }
        }

        //
        #region Private ...
        private static XRepositoryDescriptor aiMessageDescriptor = null;
        private static XRepositoryDescriptor aiProjectDescriptor = null;
        private static XRepositoryDescriptor aiConversationDescriptor = null;

        /// <summary>
        /// a LogTag for Service ...
        /// </summary>
        private static string XLogTag = XAiModelsConstants.XAiModelsDILogTag;

        /// <summary>
        /// print a log in Console ...
        /// </summary>
        /// <param name="message"></param>
        private static void Log(string message)
        {
            Console.WriteLine($"{XLogTag} => {message}");
        }

        /// <summary>
        /// Register Service ...
        /// </summary>
        /// <param name="services"></param>
        /// <param name="repositoryType"></param>
        /// <param name="contextType"></param>
        /// <param name="lifeTime"></param>
        private static void AddAiDataService(
            IServiceCollection services,
            XRepositoryType repositoryType,
            Type contextType = null,
            ServiceLifetime lifeTime = ServiceLifetime.Scoped
        )
        {
            //
            var exception = XException.InvalidArgs.ToException(); ;

            //
            // Services ...
            var isServicesExists = !services.IsNull();
            if (!isServicesExists)
            {
                //
                Log("Service Registration failed, services not provided ...");
                throw exception;
            }

            //
            // Lifetime ...
            var isLifetimeExists = !lifeTime.IsNull();
            if (!isLifetimeExists)
            {
                //
                Log("Service Registration failed, lifetime not provided ...");
                throw exception;
            }

            //
            // Validate Repository Type ...
            var isRepositoryTypeValid =
                (repositoryType == XRepositoryType.EF &&
                !contextType.IsNull()) ||
                ((repositoryType == XRepositoryType.Mongo ||
                    repositoryType == XRepositoryType.InMemory) &&
                contextType.IsNull());
            if (!isRepositoryTypeValid)
            {
                //
                Log("Service Registration failed, Invalid Repository Type and Context Type ...");
                throw exception;
            }

            //
            // Maske Instance of Repository Descriptor 
            // Based on Provided Type ...
            switch (repositoryType)
            {
                //
                case XRepositoryType.EF:
                    //
                    // XAiMessage ...
                    aiMessageDescriptor = typeof(XAiModelsHelper)
                        .InvokeGenericMethod<XRepositoryDescriptor>(
                            args: null,
                            runtimeType: contextType,
                            methodName: XAiModelsConstants.GetXAiMessageEFRepositoryDescriptor
                    );

                    //
                    // XAiProject ...
                    aiProjectDescriptor = typeof(XAiModelsHelper)
                        .InvokeGenericMethod<XRepositoryDescriptor>(
                            args: null,
                            runtimeType: contextType,
                            methodName: XAiModelsConstants.GetXAiProjectEFRepositoryDescriptor
                    );

                    //
                    // XAiConversation ...
                    aiConversationDescriptor = typeof(XAiModelsHelper)
                        .InvokeGenericMethod<XRepositoryDescriptor>(
                            args: null,
                            runtimeType: contextType,
                            methodName: XAiModelsConstants.GetXAiConversationEFRepositoryDescriptor
                    );
                    break;

                //
                case XRepositoryType.Mongo:
                    //
                    // XAiMessage ...
                    aiMessageDescriptor = XAiModelsHelper.GetXAiMessageMongoRepositoryDescriptor();

                    //
                    // XAiProject ...
                    aiProjectDescriptor = XAiModelsHelper.GetXAiProjectMongoRepositoryDescriptor();

                    //
                    // XAiConversation ...
                    aiConversationDescriptor = XAiModelsHelper.GetXAiConversationMongoRepositoryDescriptor();
                    break;

                //
                case XRepositoryType.InMemory:
                    //
                    // XAiMessage ...
                    aiMessageDescriptor = XAiModelsHelper.GetXAiMessageInMemoryRepositoryDescriptor();

                    //
                    // XAiProject ...
                    aiProjectDescriptor = XAiModelsHelper.GetXAiProjectInMemoryRepositoryDescriptor();

                    //
                    // XAiConversation ...
                    aiConversationDescriptor = XAiModelsHelper.GetXAiConversationInMemoryRepositoryDescriptor();
                    break;
            }

            //
            #region Register Repository Descriptor ...
            //
            // XAiMessage ...
            if (!aiMessageDescriptor.IsNull())
            {
                //
                services.AddXRepository(
                    lifeTime: lifeTime,
                    descriptor: aiMessageDescriptor
                );
            }

            //
            // XAiProject ...
            if (!aiProjectDescriptor.IsNull())
            {
                //
                services.AddXRepository(
                    lifeTime: lifeTime,
                    descriptor: aiProjectDescriptor
                );
            }

            //
            // XAiConversation ...
            if (!aiConversationDescriptor.IsNull())
            {
                //
                services.AddXRepository(
                    lifeTime: lifeTime,
                    descriptor: aiConversationDescriptor
                );
            }
            #endregion

            //
            #region Register Services ...
            //
            #region AiMessage ...
            //
            services.Add(
                new ServiceDescriptor(typeof(IXAiMessageServiceProvider), typeof(XAiMessageServiceProvider), lifeTime)
            );
            services.Add(
                new ServiceDescriptor(typeof(IXAiMessageRepositoryService), typeof(XAiMessageRepositoryService), lifeTime)
            );
            services.Add(
                new ServiceDescriptor(typeof(IXAiMessageRepositoryProvider), typeof(XAiMessageRepositoryProvider), lifeTime)
            );

            //
            // Enrichers ...
            services.Add(
                new ServiceDescriptor(typeof(IXAiMessageEnricher), typeof(XAiMessageEnricher), lifeTime)
            );
            #endregion

            //
            #region AiProject ...
            //
            services.Add(
                new ServiceDescriptor(typeof(IXAiProjectServiceProvider), typeof(XAiProjectServiceProvider), lifeTime)
            );
            services.Add(
                new ServiceDescriptor(typeof(IXAiProjectRepositoryService), typeof(XAiProjectRepositoryService), lifeTime)
            );
            services.Add(
                new ServiceDescriptor(typeof(IXAiProjectRepositoryProvider), typeof(XAiProjectRepositoryProvider), lifeTime)
            );

            //
            // Resource Providers ...
            services.Add(
                new ServiceDescriptor(typeof(IXAiProjectTitleResourceProvider), typeof(XAiProjectTitleResourceProvider), lifeTime)
            );
            services.Add(
                new ServiceDescriptor(typeof(IXAiProjectDescriptionResourceProvider), typeof(XAiProjectDescriptionResourceProvider), lifeTime)
            );

            //
            // Enrichers ...
            services.Add(
                new ServiceDescriptor(typeof(IXAiProjectEnricher), typeof(XAiProjectEnricher), lifeTime)
            );
            #endregion

            //
            #region AiConversation ...
            //
            services.Add(
                new ServiceDescriptor(typeof(IXAiConversationServiceProvider), typeof(XAiConversationServiceProvider), lifeTime)
            );
            services.Add(
                new ServiceDescriptor(typeof(IXAiConversationRepositoryService), typeof(XAiConversationRepositoryService), lifeTime)
            );
            services.Add(
                new ServiceDescriptor(typeof(IXAiConversationRepositoryProvider), typeof(XAiConversationRepositoryProvider), lifeTime)
            );

            //
            // Resource Providers ...
            services.Add(
                new ServiceDescriptor(typeof(IXAiConversationTitleResourceProvider), typeof(XAiConversationTitleResourceProvider), lifeTime)
            );

            //
            // Enrichers ...
            services.Add(
                new ServiceDescriptor(typeof(IXAiConversationEnricher), typeof(XAiConversationEnricher), lifeTime)
            );
            #endregion
            #endregion

            //
            // Data Provider ...
            services.Add(
                new ServiceDescriptor(typeof(IXAiDataProvider), typeof(XAiDataProvider), lifeTime)
            );

            //
            Log("Service Regitration Succeed ...");
        }
        #endregion        
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Extensions\ChatResponseExtensions.cs

using Microsoft.Extensions.AI;
using xCommons.Extensions;

namespace xAiModels.Extensions
{
    public static class ChatResponseExtensions
    {
        /// <summary>
        /// Validate a Chat Response ...
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsValid(this ChatResponse source)
        {
            //
            var result =
                source is not null &&
                !source.IsNullOrDefault() &&
                !string.IsNullOrWhiteSpace(source.Text) &&
                source.FinishReason == ChatFinishReason.Stop;

            //
            return result;
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Extensions\ChatRoleExtensions.cs

using Microsoft.Extensions.AI;
using xAiModels.Constants;
using xCommons.Extensions;

namespace xAiModels.Extensions
{
    /// <summary>
    /// Extensions for ChatRole ...
    /// </summary>
    public static class ChatRoleExtensions
    {
        /// <summary>
        /// Normalize an String to Propper Chat Role Value ...
        /// <see cref="XAiChatRoles"/>>
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static string GetChatRoleTitle(this string source)
        {
            //
            var result = XAiChatRoles.None;

            //
            if (source.ToNormalString() == XAiChatRoles.None.ToNormalString())
            {
                result = XAiChatRoles.None;
            }
            else if (source.ToNormalString() == XAiChatRoles.User.ToNormalString())
            {
                result = XAiChatRoles.User;
            }
            else if (source.ToNormalString() == XAiChatRoles.Tool.ToNormalString())
            {
                result = XAiChatRoles.Tool;
            }
            else if (source.ToNormalString() == XAiChatRoles.System.ToNormalString())
            {
                result = XAiChatRoles.System;
            }
            else if (source.ToNormalString() == XAiChatRoles.Assistant.ToNormalString())
            {
                result = XAiChatRoles.Assistant;
            }

            //
            return result;
        }

        /// <summary>
        /// Get Chat Role Title from Chat Role Class ...
        /// <see cref="XAiChatRoles"/>
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static string GetRole(this ChatRole source)
        {
            //
            var result = source.Value
                .GetChatRoleTitle();

            //
            return result;
        }

        /// <summary>
        /// Generate Chat Role Class Based on role Title ...
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static ChatRole ToChatRole(this string source)
        {
            //
            var role = source.GetChatRoleTitle();
            ChatRole result = new ChatRole(role);

            //
            return result;
        }

        /// <summary>
        /// Generate Chat Role Class Based on role Title ...
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static ChatRole ToChatRole(this XAiChatRole source)
        {
            //
            var role = source.GetStringValue();
            ChatRole result = new ChatRole(role);

            //
            return result;
        }

        /// <summary>
        /// Extract Chat Role Enum from Chat Role ...
        /// <see cref="ChatRole"/>
        /// <see cref="XAiChatRole"/>
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static XAiChatRole ToChatRole(this ChatRole source)
        {
            //
            var role = source
                .GetRole()
                .GetChatRoleTitle();

            //
            XAiChatRole result = XAiChatRole.User;

            //
            if (role == XAiChatRoles.User)
            {
                result = XAiChatRole.User;
            }
            else if (role == XAiChatRoles.Tool)
            {
                result = XAiChatRole.Tool;
            }
            else if (role == XAiChatRoles.System)
            {
                result = XAiChatRole.System;
            }
            else if (role == XAiChatRoles.Assistant)
            {
                result = XAiChatRole.Assistant;
            }

            //
            return result;
        }

        /// <summary>
        /// Validate an String as Chat Role ...
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsValid(this string source)
        {
            //
            var result =
                source != XAiChatRoles.None &&
                source.ToNormalString() != XAiChatRoles.None.ToNormalString();

            //
            return result;
        }

        /// <summary>
        /// Validate Role ...
        /// <see cref="XAiChatRole"/>>
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsValid(this XAiChatRole source)
        {
            //
            var result = source != XAiChatRole.None;
            return result;
        }

        /// <summary>
        /// Validate Role ...
        /// <see cref="ChatRole"/>>
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsValid(this ChatRole source)
        {
            //
            var role = source.ToChatRole();
            var result = role.IsValid();
            return result;
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Extensions\XAiApiConfigurationExtensions.cs

using System.Linq;
using xAiModels.Configurations;
using xAiModels.Models;
using xCommons.Extensions;

namespace xAiModels.Extensions
{
    public static class XAiApiConfigurationExtensions
    {
        /// <summary>
        /// Validate Api Configuration ...
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsValid(this XAiApiConfiguration source)
        {
            return
                !source.IsNullOrDefault() &&
                source.Models.HasChild();
        }

        /// <summary>
        /// Retrieve Default Model Descriptor ...
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static XAiModelDescriptor GetDefaultModel(this XAiApiConfiguration source)
        {
            //
            XAiModelDescriptor result = null;

            //
            if (source.IsValid())
            {
                result = source.Models.First();
            }

            //
            return result;
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Extensions\XAiModelsExtensions.cs

using xAiModels.Models.Dtos;
using xAiModels.Models.Entities;
using xCommons.Extensions;

namespace xAiModels.Extensions
{
    /// <summary>
    /// Extensions Methods for XAiModels ...
    /// </summary>
    public static class XAiModelsExtensions
    {
        /// <summary>
        /// Validate a Project for Add Or Update ...
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsValidForAddOrUpdate(this XAiProject source)
        {
            //
            var result =
                !source.IsNullOrDefault() &&
                !source.OwnerId.IsNullOrEmpty() &&
                !source.Title.IsNullOrEmpty() &&
                !source.Prompt.IsNullOrEmpty();

            //
            return result;
        }
        public static bool IsValidForAddOrUpdate(this XAiProjectDto source)
        {
            //
            var result =
                !source.IsNullOrDefault() &&
                !source.OwnerId.IsNullOrEmpty() &&
                !source.Title.IsNullOrEmpty() &&
                !source.Prompt.IsNullOrEmpty();

            //
            return result;
        }

        /// <summary>
        /// Validate a Conversation for Add Or Update ...
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsValidForAddOrUpdate(this XAiConversation source)
        {
            //
            var result =
                !source.IsNullOrDefault() &&
                !source.OwnerId.IsNullOrEmpty() &&
                !source.Title.IsNullOrEmpty() &&
                !source.ProjectId.IsNull() &&
                !source.ProjectId.IsDefaultGuid();

            //
            return result;
        }
        public static bool IsValidForAddOrUpdate(this XAiConversationDto source)
        {
            //
            var result =
                !source.IsNullOrDefault() &&
                !source.OwnerId.IsNullOrEmpty() &&
                !source.Title.IsNullOrEmpty() &&
                !source.ProjectId.IsNull() &&
                !source.ProjectId.IsDefaultGuid();

            //
            return result;
        }

        /// <summary>
        /// Validate a Message for Add Or Update ...
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsValidForAddOrUpdate(this XAiMessage source)
        {
            //
            var result =
                !source.IsNullOrDefault() &&
                !source.OwnerId.IsNullOrEmpty() &&
                source.Role.IsValid() &&
                !source.ConversationId.IsNull() &&
                !source.ConversationId.IsDefaultGuid() &&
                !source.Content.IsNullOrEmpty();

            //
            return result;
        }
        public static bool IsValidForAddOrUpdate(this XAiMessageDto source)
        {
            //
            var result =
                !source.IsNullOrDefault() &&
                !source.OwnerId.IsNullOrEmpty() &&
                source.Role.IsValid() &&
                !source.ConversationId.IsNull() &&
                !source.ConversationId.IsDefaultGuid() &&
                !source.Content.IsNullOrEmpty();

            //
            return result;
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Helpers\XAiModelsHelper.cs

using System;
using GraphQL.Types;
using xAiModels.Interfaces.Entities;
using xAiModels.Models.Entities;
using xAiModels.Providers.Entities;
using xAiModels.Providers.GraphQL;
using xDataService.Db;
using xDataService.Models;
using xDataService.Providers;

namespace xAiModels.Helpers
{
    public static class XAiModelsHelper
    {
        //
        #region XAiConversation ...
        //
        #region EF Repository Descriptor Provider(s) ...
        public static XEFRepositoryDescriptor<
            XAiConversation,
            Guid,
            IXAiConversationKeyGenerator,
            XAiConversationKeyGenerator,
            IXAiConversationRepositoryEvents,
            XAiConversationRepositoryEvents,
            XAiConversationGraphType,
            GuidGraphType,
            XAiConversationGraphQuery,
            IXAiConversationGraphQLTypeHelper,
            XAiConversationGraphQLTypeHelper,
            XAiConversationGraphSchema,
            IXAiConversationRepository,
            XAiConversationEFRepository<TContext>,
            TContext
        > GetXAiConversationEFRepositoryDescriptor<TContext>()
        where TContext : XDbContext
        {
            //
            var result = new XEFRepositoryDescriptor<
                XAiConversation,
                Guid,
                IXAiConversationKeyGenerator,
                XAiConversationKeyGenerator,
                IXAiConversationRepositoryEvents,
                XAiConversationRepositoryEvents,
                XAiConversationGraphType,
                GuidGraphType,
                XAiConversationGraphQuery,
                IXAiConversationGraphQLTypeHelper,
                XAiConversationGraphQLTypeHelper,
                XAiConversationGraphSchema,
                IXAiConversationRepository,
                XAiConversationEFRepository<TContext>,
                TContext
            >();

            //
            return result;
        }

        public static XEFRepositoryDescriptor<
            XAiConversation,
            Guid,
            IXAiConversationKeyGenerator,
            XAiConversationKeyGenerator,
            IXAiConversationRepositoryEvents,
            XAiConversationRepositoryEvents,
            XAiConversationGraphType,
            GuidGraphType,
            XAiConversationGraphQuery,
            IXAiConversationGraphQLTypeHelper,
            XAiConversationGraphQLTypeHelper,
            XAiConversationGraphSchema,
            IXAiConversationRepository,
            XAiConversationEFRepository<TContext>,
            TContext,
            IXAiConversationSeeder,
            TSeederImplementation
        > GetXAiConversationEFRepositoryDescriptor<TContext, TSeederImplementation>()
        where TContext : XDbContext
        where TSeederImplementation : XBaseDbSeeder<XAiConversation, Guid>
        {
            //
            var result = new XEFRepositoryDescriptor<
                XAiConversation,
                Guid,
                IXAiConversationKeyGenerator,
                XAiConversationKeyGenerator,
                IXAiConversationRepositoryEvents,
                XAiConversationRepositoryEvents,
                XAiConversationGraphType,
                GuidGraphType,
                XAiConversationGraphQuery,
                IXAiConversationGraphQLTypeHelper,
                XAiConversationGraphQLTypeHelper,
                XAiConversationGraphSchema,
                IXAiConversationRepository,
                XAiConversationEFRepository<TContext>,
                TContext,
                IXAiConversationSeeder,
                TSeederImplementation
            >();

            //
            return result;
        }
        #endregion

        //
        #region Mongo Repository Descriptor Provider(s) ...
        public static XMongoRepositoryDescriptor<
            XAiConversation,
            Guid,
            IXAiConversationKeyGenerator,
            XAiConversationKeyGenerator,
            IXAiConversationRepositoryEvents,
            XAiConversationRepositoryEvents,
            XAiConversationGraphType,
            GuidGraphType,
            XAiConversationGraphQuery,
            IXAiConversationGraphQLTypeHelper,
            XAiConversationGraphQLTypeHelper,
            XAiConversationGraphSchema,
            IXAiConversationRepository,
            XAiConversationMongoRepository
        > GetXAiConversationMongoRepositoryDescriptor()
        {
            //
            var result = new XMongoRepositoryDescriptor<
                XAiConversation,
                Guid,
                IXAiConversationKeyGenerator,
                XAiConversationKeyGenerator,
                IXAiConversationRepositoryEvents,
                XAiConversationRepositoryEvents,
                XAiConversationGraphType,
                GuidGraphType,
                XAiConversationGraphQuery,
                IXAiConversationGraphQLTypeHelper,
                XAiConversationGraphQLTypeHelper,
                XAiConversationGraphSchema,
                IXAiConversationRepository,
                XAiConversationMongoRepository
            >();

            //
            return result;
        }

        public static XMongoRepositoryDescriptor<
            XAiConversation,
            Guid,
            IXAiConversationKeyGenerator,
            XAiConversationKeyGenerator,
            IXAiConversationRepositoryEvents,
            XAiConversationRepositoryEvents,
            XAiConversationGraphType,
            GuidGraphType,
            XAiConversationGraphQuery,
            IXAiConversationGraphQLTypeHelper,
            XAiConversationGraphQLTypeHelper,
            XAiConversationGraphSchema,
            IXAiConversationRepository,
            XAiConversationMongoRepository,
            IXAiConversationSeeder,
            TSeederImplementation
        > GetXAiConversationMongoRepositoryDescriptor<TSeederImplementation>()
        where TSeederImplementation : XBaseDbSeeder<XAiConversation, Guid>
        {
            //
            var result = new XMongoRepositoryDescriptor<
                XAiConversation,
                Guid,
                IXAiConversationKeyGenerator,
                XAiConversationKeyGenerator,
                IXAiConversationRepositoryEvents,
                XAiConversationRepositoryEvents,
                XAiConversationGraphType,
                GuidGraphType,
                XAiConversationGraphQuery,
                IXAiConversationGraphQLTypeHelper,
                XAiConversationGraphQLTypeHelper,
                XAiConversationGraphSchema,
                IXAiConversationRepository,
                XAiConversationMongoRepository,
                IXAiConversationSeeder,
                TSeederImplementation
            >();

            //
            return result;
        }
        #endregion

        //
        #region InMemory Repository Descriptor Provider(s) ...
        public static XInMemoryRepositoryDescriptor<
            XAiConversation,
            Guid,
            IXAiConversationKeyGenerator,
            XAiConversationKeyGenerator,
            IXAiConversationRepositoryEvents,
            XAiConversationRepositoryEvents,
            XAiConversationGraphType,
            GuidGraphType,
            XAiConversationGraphQuery,
            IXAiConversationGraphQLTypeHelper,
            XAiConversationGraphQLTypeHelper,
            XAiConversationGraphSchema,
            IXAiConversationRepository,
            XAiConversationInMemoryRepository
        > GetXAiConversationInMemoryRepositoryDescriptor()
        {
            //
            var result = new XInMemoryRepositoryDescriptor<
                XAiConversation,
                Guid,
                IXAiConversationKeyGenerator,
                XAiConversationKeyGenerator,
                IXAiConversationRepositoryEvents,
                XAiConversationRepositoryEvents,
                XAiConversationGraphType,
                GuidGraphType,
                XAiConversationGraphQuery,
                IXAiConversationGraphQLTypeHelper,
                XAiConversationGraphQLTypeHelper,
                XAiConversationGraphSchema,
                IXAiConversationRepository,
                XAiConversationInMemoryRepository
            >();

            //
            return result;
        }

        public static XInMemoryRepositoryDescriptor<
            XAiConversation,
            Guid,
            IXAiConversationKeyGenerator,
            XAiConversationKeyGenerator,
            IXAiConversationRepositoryEvents,
            XAiConversationRepositoryEvents,
            XAiConversationGraphType,
            GuidGraphType,
            XAiConversationGraphQuery,
            IXAiConversationGraphQLTypeHelper,
            XAiConversationGraphQLTypeHelper,
            XAiConversationGraphSchema,
            IXAiConversationRepository,
            XAiConversationInMemoryRepository,
            IXAiConversationSeeder,
            TSeederImplementation
        > GetXAiConversationInMemoryRepositoryDescriptor<TSeederImplementation>()
        where TSeederImplementation : XBaseDbSeeder<XAiConversation, Guid>
        {
            //
            var result = new XInMemoryRepositoryDescriptor<
                XAiConversation,
                Guid,
                IXAiConversationKeyGenerator,
                XAiConversationKeyGenerator,
                IXAiConversationRepositoryEvents,
                XAiConversationRepositoryEvents,
                XAiConversationGraphType,
                GuidGraphType,
                XAiConversationGraphQuery,
                IXAiConversationGraphQLTypeHelper,
                XAiConversationGraphQLTypeHelper,
                XAiConversationGraphSchema,
                IXAiConversationRepository,
                XAiConversationInMemoryRepository,
                IXAiConversationSeeder,
                TSeederImplementation
            >();

            //
            return result;
        }
        #endregion        
        #endregion

        //
        #region XAiMessage ...
        //
        #region EF Repository Descriptor Provider(s) ...
        public static XEFRepositoryDescriptor<
            XAiMessage,
            Guid,
            IXAiMessageKeyGenerator,
            XAiMessageKeyGenerator,
            IXAiMessageRepositoryEvents,
            XAiMessageRepositoryEvents,
            XAiMessageGraphType,
            GuidGraphType,
            XAiMessageGraphQuery,
            IXAiMessageGraphQLTypeHelper,
            XAiMessageGraphQLTypeHelper,
            XAiMessageGraphSchema,
            IXAiMessageRepository,
            XAiMessageEFRepository<TContext>,
            TContext
        > GetXAiMessageEFRepositoryDescriptor<TContext>()
        where TContext : XDbContext
        {
            //
            var result = new XEFRepositoryDescriptor<
                XAiMessage,
                Guid,
                IXAiMessageKeyGenerator,
                XAiMessageKeyGenerator,
                IXAiMessageRepositoryEvents,
                XAiMessageRepositoryEvents,
                XAiMessageGraphType,
                GuidGraphType,
                XAiMessageGraphQuery,
                IXAiMessageGraphQLTypeHelper,
                XAiMessageGraphQLTypeHelper,
                XAiMessageGraphSchema,
                IXAiMessageRepository,
                XAiMessageEFRepository<TContext>,
                TContext
            >();

            //
            return result;
        }

        public static XEFRepositoryDescriptor<
            XAiMessage,
            Guid,
            IXAiMessageKeyGenerator,
            XAiMessageKeyGenerator,
            IXAiMessageRepositoryEvents,
            XAiMessageRepositoryEvents,
            XAiMessageGraphType,
            GuidGraphType,
            XAiMessageGraphQuery,
            IXAiMessageGraphQLTypeHelper,
            XAiMessageGraphQLTypeHelper,
            XAiMessageGraphSchema,
            IXAiMessageRepository,
            XAiMessageEFRepository<TContext>,
            TContext,
            IXAiMessageSeeder,
            TSeederImplementation
        > GetXAiMessageEFRepositoryDescriptor<TContext, TSeederImplementation>()
        where TContext : XDbContext
        where TSeederImplementation : XBaseDbSeeder<XAiMessage, Guid>
        {
            //
            var result = new XEFRepositoryDescriptor<
                XAiMessage,
                Guid,
                IXAiMessageKeyGenerator,
                XAiMessageKeyGenerator,
                IXAiMessageRepositoryEvents,
                XAiMessageRepositoryEvents,
                XAiMessageGraphType,
                GuidGraphType,
                XAiMessageGraphQuery,
                IXAiMessageGraphQLTypeHelper,
                XAiMessageGraphQLTypeHelper,
                XAiMessageGraphSchema,
                IXAiMessageRepository,
                XAiMessageEFRepository<TContext>,
                TContext,
                IXAiMessageSeeder,
                TSeederImplementation
            >();

            //
            return result;
        }
        #endregion

        //
        #region Mongo Repository Descriptor Provider(s) ...
        public static XMongoRepositoryDescriptor<
            XAiMessage,
            Guid,
            IXAiMessageKeyGenerator,
            XAiMessageKeyGenerator,
            IXAiMessageRepositoryEvents,
            XAiMessageRepositoryEvents,
            XAiMessageGraphType,
            GuidGraphType,
            XAiMessageGraphQuery,
            IXAiMessageGraphQLTypeHelper,
            XAiMessageGraphQLTypeHelper,
            XAiMessageGraphSchema,
            IXAiMessageRepository,
            XAiMessageMongoRepository
        > GetXAiMessageMongoRepositoryDescriptor()
        {
            //
            var result = new XMongoRepositoryDescriptor<
                XAiMessage,
                Guid,
                IXAiMessageKeyGenerator,
                XAiMessageKeyGenerator,
                IXAiMessageRepositoryEvents,
                XAiMessageRepositoryEvents,
                XAiMessageGraphType,
                GuidGraphType,
                XAiMessageGraphQuery,
                IXAiMessageGraphQLTypeHelper,
                XAiMessageGraphQLTypeHelper,
                XAiMessageGraphSchema,
                IXAiMessageRepository,
                XAiMessageMongoRepository
            >();

            //
            return result;
        }

        public static XMongoRepositoryDescriptor<
            XAiMessage,
            Guid,
            IXAiMessageKeyGenerator,
            XAiMessageKeyGenerator,
            IXAiMessageRepositoryEvents,
            XAiMessageRepositoryEvents,
            XAiMessageGraphType,
            GuidGraphType,
            XAiMessageGraphQuery,
            IXAiMessageGraphQLTypeHelper,
            XAiMessageGraphQLTypeHelper,
            XAiMessageGraphSchema,
            IXAiMessageRepository,
            XAiMessageMongoRepository,
            IXAiMessageSeeder,
            TSeederImplementation
        > GetXAiMessageMongoRepositoryDescriptor<TSeederImplementation>()
        where TSeederImplementation : XBaseDbSeeder<XAiMessage, Guid>
        {
            //
            var result = new XMongoRepositoryDescriptor<
                XAiMessage,
                Guid,
                IXAiMessageKeyGenerator,
                XAiMessageKeyGenerator,
                IXAiMessageRepositoryEvents,
                XAiMessageRepositoryEvents,
                XAiMessageGraphType,
                GuidGraphType,
                XAiMessageGraphQuery,
                IXAiMessageGraphQLTypeHelper,
                XAiMessageGraphQLTypeHelper,
                XAiMessageGraphSchema,
                IXAiMessageRepository,
                XAiMessageMongoRepository,
                IXAiMessageSeeder,
                TSeederImplementation
            >();

            //
            return result;
        }
        #endregion

        //
        #region InMemory Repository Descriptor Provider(s) ...
        public static XInMemoryRepositoryDescriptor<
            XAiMessage,
            Guid,
            IXAiMessageKeyGenerator,
            XAiMessageKeyGenerator,
            IXAiMessageRepositoryEvents,
            XAiMessageRepositoryEvents,
            XAiMessageGraphType,
            GuidGraphType,
            XAiMessageGraphQuery,
            IXAiMessageGraphQLTypeHelper,
            XAiMessageGraphQLTypeHelper,
            XAiMessageGraphSchema,
            IXAiMessageRepository,
            XAiMessageInMemoryRepository
        > GetXAiMessageInMemoryRepositoryDescriptor()
        {
            //
            var result = new XInMemoryRepositoryDescriptor<
                XAiMessage,
                Guid,
                IXAiMessageKeyGenerator,
                XAiMessageKeyGenerator,
                IXAiMessageRepositoryEvents,
                XAiMessageRepositoryEvents,
                XAiMessageGraphType,
                GuidGraphType,
                XAiMessageGraphQuery,
                IXAiMessageGraphQLTypeHelper,
                XAiMessageGraphQLTypeHelper,
                XAiMessageGraphSchema,
                IXAiMessageRepository,
                XAiMessageInMemoryRepository
            >();

            //
            return result;
        }

        public static XInMemoryRepositoryDescriptor<
            XAiMessage,
            Guid,
            IXAiMessageKeyGenerator,
            XAiMessageKeyGenerator,
            IXAiMessageRepositoryEvents,
            XAiMessageRepositoryEvents,
            XAiMessageGraphType,
            GuidGraphType,
            XAiMessageGraphQuery,
            IXAiMessageGraphQLTypeHelper,
            XAiMessageGraphQLTypeHelper,
            XAiMessageGraphSchema,
            IXAiMessageRepository,
            XAiMessageInMemoryRepository,
            IXAiMessageSeeder,
            TSeederImplementation
        > GetXAiMessageInMemoryRepositoryDescriptor<TSeederImplementation>()
        where TSeederImplementation : XBaseDbSeeder<XAiMessage, Guid>
        {
            //
            var result = new XInMemoryRepositoryDescriptor<
                XAiMessage,
                Guid,
                IXAiMessageKeyGenerator,
                XAiMessageKeyGenerator,
                IXAiMessageRepositoryEvents,
                XAiMessageRepositoryEvents,
                XAiMessageGraphType,
                GuidGraphType,
                XAiMessageGraphQuery,
                IXAiMessageGraphQLTypeHelper,
                XAiMessageGraphQLTypeHelper,
                XAiMessageGraphSchema,
                IXAiMessageRepository,
                XAiMessageInMemoryRepository,
                IXAiMessageSeeder,
                TSeederImplementation
            >();

            //
            return result;
        }
        #endregion
        #endregion

        //
        #region XAiProject ...
        //
        #region EF Repository Descriptor Provider(s) ...
        public static XEFRepositoryDescriptor<
            XAiProject,
            Guid,
            IXAiProjectKeyGenerator,
            XAiProjectKeyGenerator,
            IXAiProjectRepositoryEvents,
            XAiProjectRepositoryEvents,
            XAiProjectGraphType,
            GuidGraphType,
            XAiProjectGraphQuery,
            IXAiProjectGraphQLTypeHelper,
            XAiProjectGraphQLTypeHelper,
            XAiProjectGraphSchema,
            IXAiProjectRepository,
            XAiProjectEFRepository<TContext>,
            TContext
        > GetXAiProjectEFRepositoryDescriptor<TContext>()
        where TContext : XDbContext
        {
            //
            var result = new XEFRepositoryDescriptor<
                XAiProject,
                Guid,
                IXAiProjectKeyGenerator,
                XAiProjectKeyGenerator,
                IXAiProjectRepositoryEvents,
                XAiProjectRepositoryEvents,
                XAiProjectGraphType,
                GuidGraphType,
                XAiProjectGraphQuery,
                IXAiProjectGraphQLTypeHelper,
                XAiProjectGraphQLTypeHelper,
                XAiProjectGraphSchema,
                IXAiProjectRepository,
                XAiProjectEFRepository<TContext>,
                TContext
            >();

            //
            return result;
        }

        public static XEFRepositoryDescriptor<
            XAiProject,
            Guid,
            IXAiProjectKeyGenerator,
            XAiProjectKeyGenerator,
            IXAiProjectRepositoryEvents,
            XAiProjectRepositoryEvents,
            XAiProjectGraphType,
            GuidGraphType,
            XAiProjectGraphQuery,
            IXAiProjectGraphQLTypeHelper,
            XAiProjectGraphQLTypeHelper,
            XAiProjectGraphSchema,
            IXAiProjectRepository,
            XAiProjectEFRepository<TContext>,
            TContext,
            IXAiProjectSeeder,
            TSeederImplementation
        > GetXAiProjectEFRepositoryDescriptor<TContext, TSeederImplementation>()
        where TContext : XDbContext
        where TSeederImplementation : XBaseDbSeeder<XAiProject, Guid>
        {
            //
            var result = new XEFRepositoryDescriptor<
                XAiProject,
                Guid,
                IXAiProjectKeyGenerator,
                XAiProjectKeyGenerator,
                IXAiProjectRepositoryEvents,
                XAiProjectRepositoryEvents,
                XAiProjectGraphType,
                GuidGraphType,
                XAiProjectGraphQuery,
                IXAiProjectGraphQLTypeHelper,
                XAiProjectGraphQLTypeHelper,
                XAiProjectGraphSchema,
                IXAiProjectRepository,
                XAiProjectEFRepository<TContext>,
                TContext,
                IXAiProjectSeeder,
                TSeederImplementation
            >();

            //
            return result;
        }
        #endregion

        //
        #region Mongo Repository Descriptor Provider(s) ...
        public static XMongoRepositoryDescriptor<
            XAiProject,
            Guid,
            IXAiProjectKeyGenerator,
            XAiProjectKeyGenerator,
            IXAiProjectRepositoryEvents,
            XAiProjectRepositoryEvents,
            XAiProjectGraphType,
            GuidGraphType,
            XAiProjectGraphQuery,
            IXAiProjectGraphQLTypeHelper,
            XAiProjectGraphQLTypeHelper,
            XAiProjectGraphSchema,
            IXAiProjectRepository,
            XAiProjectMongoRepository
        > GetXAiProjectMongoRepositoryDescriptor()
        {
            //
            var result = new XMongoRepositoryDescriptor<
                XAiProject,
                Guid,
                IXAiProjectKeyGenerator,
                XAiProjectKeyGenerator,
                IXAiProjectRepositoryEvents,
                XAiProjectRepositoryEvents,
                XAiProjectGraphType,
                GuidGraphType,
                XAiProjectGraphQuery,
                IXAiProjectGraphQLTypeHelper,
                XAiProjectGraphQLTypeHelper,
                XAiProjectGraphSchema,
                IXAiProjectRepository,
                XAiProjectMongoRepository
            >();

            //
            return result;
        }

        public static XMongoRepositoryDescriptor<
            XAiProject,
            Guid,
            IXAiProjectKeyGenerator,
            XAiProjectKeyGenerator,
            IXAiProjectRepositoryEvents,
            XAiProjectRepositoryEvents,
            XAiProjectGraphType,
            GuidGraphType,
            XAiProjectGraphQuery,
            IXAiProjectGraphQLTypeHelper,
            XAiProjectGraphQLTypeHelper,
            XAiProjectGraphSchema,
            IXAiProjectRepository,
            XAiProjectMongoRepository,
            IXAiProjectSeeder,
            TSeederImplementation
        > GetXAiProjectMongoRepositoryDescriptor<TSeederImplementation>()
        where TSeederImplementation : XBaseDbSeeder<XAiProject, Guid>
        {
            //
            var result = new XMongoRepositoryDescriptor<
                XAiProject,
                Guid,
                IXAiProjectKeyGenerator,
                XAiProjectKeyGenerator,
                IXAiProjectRepositoryEvents,
                XAiProjectRepositoryEvents,
                XAiProjectGraphType,
                GuidGraphType,
                XAiProjectGraphQuery,
                IXAiProjectGraphQLTypeHelper,
                XAiProjectGraphQLTypeHelper,
                XAiProjectGraphSchema,
                IXAiProjectRepository,
                XAiProjectMongoRepository,
                IXAiProjectSeeder,
                TSeederImplementation
            >();

            //
            return result;
        }
        #endregion

        //
        #region InMemory Repository Descriptor Provider(s) ...
        public static XInMemoryRepositoryDescriptor<
            XAiProject,
            Guid,
            IXAiProjectKeyGenerator,
            XAiProjectKeyGenerator,
            IXAiProjectRepositoryEvents,
            XAiProjectRepositoryEvents,
            XAiProjectGraphType,
            GuidGraphType,
            XAiProjectGraphQuery,
            IXAiProjectGraphQLTypeHelper,
            XAiProjectGraphQLTypeHelper,
            XAiProjectGraphSchema,
            IXAiProjectRepository,
            XAiProjectInMemoryRepository
        > GetXAiProjectInMemoryRepositoryDescriptor()
        {
            //
            var result = new XInMemoryRepositoryDescriptor<
                XAiProject,
                Guid,
                IXAiProjectKeyGenerator,
                XAiProjectKeyGenerator,
                IXAiProjectRepositoryEvents,
                XAiProjectRepositoryEvents,
                XAiProjectGraphType,
                GuidGraphType,
                XAiProjectGraphQuery,
                IXAiProjectGraphQLTypeHelper,
                XAiProjectGraphQLTypeHelper,
                XAiProjectGraphSchema,
                IXAiProjectRepository,
                XAiProjectInMemoryRepository
            >();

            //
            return result;
        }

        public static XInMemoryRepositoryDescriptor<
            XAiProject,
            Guid,
            IXAiProjectKeyGenerator,
            XAiProjectKeyGenerator,
            IXAiProjectRepositoryEvents,
            XAiProjectRepositoryEvents,
            XAiProjectGraphType,
            GuidGraphType,
            XAiProjectGraphQuery,
            IXAiProjectGraphQLTypeHelper,
            XAiProjectGraphQLTypeHelper,
            XAiProjectGraphSchema,
            IXAiProjectRepository,
            XAiProjectInMemoryRepository,
            IXAiProjectSeeder,
            TSeederImplementation
        > GetXAiProjectInMemoryRepositoryDescriptor<TSeederImplementation>()
        where TSeederImplementation : XBaseDbSeeder<XAiProject, Guid>
        {
            //
            var result = new XInMemoryRepositoryDescriptor<
                XAiProject,
                Guid,
                IXAiProjectKeyGenerator,
                XAiProjectKeyGenerator,
                IXAiProjectRepositoryEvents,
                XAiProjectRepositoryEvents,
                XAiProjectGraphType,
                GuidGraphType,
                XAiProjectGraphQuery,
                IXAiProjectGraphQLTypeHelper,
                XAiProjectGraphQLTypeHelper,
                XAiProjectGraphSchema,
                IXAiProjectRepository,
                XAiProjectInMemoryRepository,
                IXAiProjectSeeder,
                TSeederImplementation
            >();

            //
            return result;
        }
        #endregion        
        #endregion    
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\IXAiConversationTitleResourceProvider.cs

using xStringService.Interfaces;

namespace xAiModels.Interfaces
{
    public interface IXAiConversationTitleResourceProvider : IXOwnedItemResourceProvider
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\IXAiDataProvider.cs

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using xAiModels.Models.Dtos;

namespace xAiModels.Interfaces
{
    /// <summary>
    /// All Data Manipulation Requirements ...
    /// </summary>
    public interface IXAiDataProvider
    {
        //
        #region Loaders ...
        /// <summary>
        /// Load Projects List of Specified Owner ...
        /// </summary>
        /// <param name="ownerId"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<IEnumerable<XAiProjectDto>> LoadProjects(
            string ownerId,
            bool fillConversations = false,
            bool fillConversationMessages = false,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Load List of Conversations for Specified Project ... 
        /// </summary>
        /// <param name="projectId"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<IEnumerable<XAiConversationDto>> LoadConversations(
            Guid projectId,
            bool fillMessages = false,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Load Messages of Specified Conversation ...
        /// </summary>
        /// <param name="conversationId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<IEnumerable<XAiMessageDto>> LoadMessages(
            Guid conversationId,
            CancellationToken cancellationToken = default
        );
        #endregion

        //
        #region Creator/Adders ...
        /// <summary>
        /// Create Project ...
        /// </summary>
        /// <param name="ownerId"></param>
        /// <param name="item"></param>
        /// <param name="connectionId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XAiProjectDto> CreateProject(
            string ownerId,
            XAiProjectDto item = null,
            string connectionId = null,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Create Conversation ...
        /// </summary>
        /// <param name="projectId"></param>
        /// <param name="item"></param>
        /// <param name="connectionId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XAiConversationDto> CreateConversation(
            Guid projectId,
            XAiConversationDto item = null,
            string connectionId = null,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Add Message ...
        /// </summary>
        /// <param name="conversationId"></param>
        /// <param name="item"></param>
        /// <param name="connectionId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XAiMessageDto> AddMessage(
            Guid conversationId,
            XAiMessageDto item,
            string connectionId = null,
            CancellationToken cancellationToken = default
        );
        #endregion    
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\IXAiProjectDescriptionResourceProvider.cs

using xStringService.Interfaces;

namespace xAiModels.Interfaces
{
    public interface IXAiProjectDescriptionResourceProvider : IXOwnedItemResourceProvider
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\IXAiProjectTitleResourceProvider.cs

using xStringService.Interfaces;

namespace xAiModels.Interfaces
{
    public interface IXAiProjectTitleResourceProvider : IXOwnedItemResourceProvider
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Dtos\IXAiConversationDtoHub.cs

using System;
using xAiModels.Models.Dtos;
using xAiModels.Models.Entities;
using xPushService.Interfaces;

namespace xAiModels.Interfaces.Dtos
{
    public interface IXAiConversationDtoHub : IXBaseDtoHub<XAiConversation, XAiConversationDto, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Dtos\IXAiConversationRepositoryService.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Query;
using xAiModels.Models.Dtos;
using xAiModels.Models.Entities;
using xDataService.Interfaces;
using xModels.Dtos;

namespace xAiModels.Interfaces.Dtos
{
    public interface IXAiConversationRepositoryService : IXBaseRepositoryService<XAiConversation, XAiConversationDto, Guid>
    {
        //
        #region Actions ...
        //
        #region Add ...
        /// <summary>
        /// add a new Dto ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="saveChanges"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XAiConversationDto> AddAsync(
            XAiConversationDto item,
            bool saveChanges = true,
            bool fillMessages = true,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// add or update a Dto (add if not exists/update if exists) ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="saveChanges"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XAiConversationDto> AddOrUpdateAsync(
            XAiConversationDto item,
            bool saveChanges = true,
            bool fillMessages = true,
            CancellationToken cancellationToken = default
        );
        #endregion

        //
        #region Update ...
        /// <summary>
        /// Update an Dto values ...
        /// </summary>
        /// <param name="id"></param>
        /// <param name="item"></param>
        /// <param name="saveChanges"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XAiConversationDto> UpdateAsync(
            Guid id,
            XAiConversationDto item,
            bool saveChanges = true,
            bool fillMessages = true,
            CancellationToken cancellationToken = default
        );
        #endregion

        //
        #region Remove ...
        /// <summary>
        /// remove an Dto by it's Id ...
        /// </summary>
        /// <param name="id"></param>
        /// <param name="softDelete"></param>
        /// <param name="saveChanges"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XAiConversationDto> RemoveAsync(
            Guid id,
            bool softDelete = false,
            bool saveChanges = true,
            bool fillMessages = true,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// remove an Dto ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="softDelete"></param>
        /// <param name="saveChanges"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XAiConversationDto> RemoveAsync(
            XAiConversationDto item,
            bool softDelete = false,
            bool saveChanges = true,
            bool fillMessages = true,
            CancellationToken cancellationToken = default
        );
        #endregion

        //
        #region Retrieve ...
        /// <summary>
        /// retrieve an Dto by it's Id ...
        /// </summary>
        /// <param name="id"></param>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XAiConversationDto> GetAsync(
            Guid id,
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiConversation>, IIncludableQueryable<XAiConversation, object>> includeBuilder = null,
            bool fillMessages = true,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// retrieve all exists Dtos ...
        /// </summary>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="orderBuilder"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<IEnumerable<XAiConversationDto>> GetAllAsync(
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiConversation>, IOrderedQueryable<XAiConversation>> orderBuilder = null,
            Func<IQueryable<XAiConversation>, IIncludableQueryable<XAiConversation, object>> includeBuilder = null,
            bool fillMessages = true,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// retrieve all exists Dtos
        /// as Async Enumerable ...
        /// </summary>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="orderBuilder"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        IAsyncEnumerable<XAiConversationDto> GetAllAsAsyncEnumerable(
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiConversation>, IOrderedQueryable<XAiConversation>> orderBuilder = null,
            Func<IQueryable<XAiConversation>, IIncludableQueryable<XAiConversation, object>> includeBuilder = null,
            bool fillMessages = true,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// find an Dto by providing a Conditional Expression ...
        /// </summary>
        /// <param name="predicate"></param>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XAiConversationDto> FindOneAsync(
            Expression<Func<XAiConversation, bool>> predicate,
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiConversation>, IIncludableQueryable<XAiConversation, object>> includeBuilder = null,
            bool fillMessages = true,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// find a collection of Dtos by proving a Conditional Expression ...
        /// </summary>
        /// <param name="predicate"></param>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="orderBuilder"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<IEnumerable<XAiConversationDto>> FindManyAsync(
            Expression<Func<XAiConversation, bool>> predicate,
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiConversation>, IOrderedQueryable<XAiConversation>> orderBuilder = null,
            Func<IQueryable<XAiConversation>, IIncludableQueryable<XAiConversation, object>> includeBuilder = null,
            bool fillMessages = true,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// retrieve Dtos based on XQuery Pagination structure ...
        /// </summary>
        /// <param name="query"></param>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="predicate"></param>
        /// <param name="orderBuilder"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XQueryResult<XAiConversationDto>> QueryAsync(
            XQuery query,
            bool ignoreSoftDeleteds = true,
            Expression<Func<XAiConversation, bool>> predicate = null,
            Func<IQueryable<XAiConversation>, IOrderedQueryable<XAiConversation>> orderBuilder = null,
            Func<IQueryable<XAiConversation>, IIncludableQueryable<XAiConversation, object>> includeBuilder = null,
            bool fillMessages = true,
            CancellationToken cancellationToken = default
        );
        #endregion
        #endregion        
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Dtos\IXAiConversationServiceProvider.cs

using System;
using xAiModels.Models.Dtos;
using xAiModels.Models.Entities;
using xAiModels.Providers.Dtos;
using xPushService.Interfaces;

namespace xAiModels.Interfaces.Dtos
{
    public interface IXAiConversationServiceProvider : IXBaseDtoProvider<XAiConversation, XAiConversationDto, Guid, XAiConversationDtoHub>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Dtos\IXAiMessageDtoHub.cs

using System;
using xAiModels.Models.Dtos;
using xAiModels.Models.Entities;
using xPushService.Interfaces;

namespace xAiModels.Interfaces.Dtos
{
    public interface IXAiMessageDtoHub : IXBaseDtoHub<XAiMessage, XAiMessageDto, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Dtos\IXAiMessageRepositoryService.cs

using System;
using xAiModels.Models.Dtos;
using xAiModels.Models.Entities;
using xDataService.Interfaces;

namespace xAiModels.Interfaces.Dtos
{
    public interface IXAiMessageRepositoryService : IXBaseRepositoryService<XAiMessage, XAiMessageDto, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Dtos\IXAiMessageServiceProvider.cs

using System;
using xAiModels.Models.Dtos;
using xAiModels.Models.Entities;
using xAiModels.Providers.Dtos;
using xPushService.Interfaces;

namespace xAiModels.Interfaces.Dtos
{
    public interface IXAiMessageServiceProvider : IXBaseDtoProvider<XAiMessage, XAiMessageDto, Guid, XAiMessageDtoHub>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Dtos\IXAiProjectDtoHub.cs

using System;
using xAiModels.Models.Dtos;
using xAiModels.Models.Entities;
using xPushService.Interfaces;

namespace xAiModels.Interfaces.Dtos
{
    public interface IXAiProjectDtoHub : IXBaseDtoHub<XAiProject, XAiProjectDto, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Dtos\IXAiProjectRepositoryService.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Query;
using xAiModels.Models.Dtos;
using xAiModels.Models.Entities;
using xDataService.Interfaces;
using xModels.Dtos;

namespace xAiModels.Interfaces.Dtos
{
    public interface IXAiProjectRepositoryService : IXBaseRepositoryService<XAiProject, XAiProjectDto, Guid>
    {
        //
        #region Actions ...
        //
        #region Add ...
        /// <summary>
        /// add a new Dto ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="saveChanges"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XAiProjectDto> AddAsync(
            XAiProjectDto item,
            bool saveChanges = true,
            bool fillConversations = true,
            bool fillConversationMessages = true,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// add or update a Dto (add if not exists/update if exists) ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="saveChanges"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XAiProjectDto> AddOrUpdateAsync(
            XAiProjectDto item,
            bool saveChanges = true,
            bool fillConversations = true,
            bool fillConversationMessages = true,
            CancellationToken cancellationToken = default
        );
        #endregion

        //
        #region Update ...
        /// <summary>
        /// Update an Dto values ...
        /// </summary>
        /// <param name="id"></param>
        /// <param name="item"></param>
        /// <param name="saveChanges"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XAiProjectDto> UpdateAsync(
            Guid id,
            XAiProjectDto item,
            bool saveChanges = true,
            bool fillConversations = true,
            bool fillConversationMessages = true,
            CancellationToken cancellationToken = default
        );
        #endregion

        //
        #region Remove ...
        /// <summary>
        /// remove an Dto by it's Id ...
        /// </summary>
        /// <param name="id"></param>
        /// <param name="softDelete"></param>
        /// <param name="saveChanges"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XAiProjectDto> RemoveAsync(
            Guid id,
            bool softDelete = false,
            bool saveChanges = true,
            bool fillConversations = true,
            bool fillConversationMessages = true,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// remove an Dto ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="softDelete"></param>
        /// <param name="saveChanges"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XAiProjectDto> RemoveAsync(
            XAiProjectDto item,
            bool softDelete = false,
            bool saveChanges = true,
            bool fillConversations = true,
            bool fillConversationMessages = true,
            CancellationToken cancellationToken = default
        );
        #endregion

        //
        #region Retrieve ...
        /// <summary>
        /// retrieve an Dto by it's Id ...
        /// </summary>
        /// <param name="id"></param>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XAiProjectDto> GetAsync(
            Guid id,
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiProject>, IIncludableQueryable<XAiProject, object>> includeBuilder = null,
            bool fillConversations = true,
            bool fillConversationMessages = true,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// retrieve all exists Dtos ...
        /// </summary>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="orderBuilder"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<IEnumerable<XAiProjectDto>> GetAllAsync(
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiProject>, IOrderedQueryable<XAiProject>> orderBuilder = null,
            Func<IQueryable<XAiProject>, IIncludableQueryable<XAiProject, object>> includeBuilder = null,
            bool fillConversations = true,
            bool fillConversationMessages = true,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// retrieve all exists Dtos
        /// as Async Enumerable ...
        /// </summary>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="orderBuilder"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        IAsyncEnumerable<XAiProjectDto> GetAllAsAsyncEnumerable(
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiProject>, IOrderedQueryable<XAiProject>> orderBuilder = null,
            Func<IQueryable<XAiProject>, IIncludableQueryable<XAiProject, object>> includeBuilder = null,
            bool fillConversations = true,
            bool fillConversationMessages = true,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// find an Dto by providing a Conditional Expression ...
        /// </summary>
        /// <param name="predicate"></param>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XAiProjectDto> FindOneAsync(
            Expression<Func<XAiProject, bool>> predicate,
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiProject>, IIncludableQueryable<XAiProject, object>> includeBuilder = null,
            bool fillConversations = true,
            bool fillConversationMessages = true,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// find a collection of Dtos by proving a Conditional Expression ...
        /// </summary>
        /// <param name="predicate"></param>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="orderBuilder"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<IEnumerable<XAiProjectDto>> FindManyAsync(
            Expression<Func<XAiProject, bool>> predicate,
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiProject>, IOrderedQueryable<XAiProject>> orderBuilder = null,
            Func<IQueryable<XAiProject>, IIncludableQueryable<XAiProject, object>> includeBuilder = null,
            bool fillConversations = true,
            bool fillConversationMessages = true,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// retrieve Dtos based on XQuery Pagination structure ...
        /// </summary>
        /// <param name="query"></param>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="predicate"></param>
        /// <param name="orderBuilder"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XQueryResult<XAiProjectDto>> QueryAsync(
            XQuery query,
            bool ignoreSoftDeleteds = true,
            Expression<Func<XAiProject, bool>> predicate = null,
            Func<IQueryable<XAiProject>, IOrderedQueryable<XAiProject>> orderBuilder = null,
            Func<IQueryable<XAiProject>, IIncludableQueryable<XAiProject, object>> includeBuilder = null,
            bool fillConversations = true,
            bool fillConversationMessages = true,
            CancellationToken cancellationToken = default
        );
        #endregion
        #endregion        
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Dtos\IXAiProjectServiceProvider.cs

using System;
using xAiModels.Models.Dtos;
using xAiModels.Models.Entities;
using xAiModels.Providers.Dtos;
using xPushService.Interfaces;

namespace xAiModels.Interfaces.Dtos
{
    public interface IXAiProjectServiceProvider : IXBaseDtoProvider<XAiProject, XAiProjectDto, Guid, XAiProjectDtoHub>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Enrichers\IXAiConversationEnricher.cs

using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using xAiModels.Models.Dtos;
using xDataService.Interfaces;

namespace xAiModels.Interfaces.Enrichers
{
    /// <summary>
    /// Ai Conversation Enricher ...
    /// </summary>
    public interface IXAiConversationEnricher : IXBaseEnricher<XAiConversationDto, XAiConversationDto>
    {
        /// <summary>
        /// Converts a Signle Entity to Dto ...
        /// </summary>
        /// <param name="source"></param>
        /// <param name="destination"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task EnrichAsync(
            XAiConversationDto source,
            XAiConversationDto destination,
            bool fillMessages = false,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Converts a Collection of Entities to Dtos ...
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="destinations"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task EnrichRangeAsync(
            IEnumerable<XAiConversationDto> sources,
            IEnumerable<XAiConversationDto> destinations,
            bool fillMessages = false,
            CancellationToken cancellationToken = default
        );
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Enrichers\IXAiMessageEnricher.cs

using xAiModels.Models.Dtos;
using xDataService.Interfaces;

namespace xAiModels.Interfaces.Enrichers
{
    public interface IXAiMessageEnricher : IXBaseEnricher<XAiMessageDto, XAiMessageDto>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Enrichers\IXAiProjectEnricher.cs

using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using xAiModels.Models.Dtos;
using xDataService.Interfaces;

namespace xAiModels.Interfaces.Enrichers
{
    public interface IXAiProjectEnricher : IXBaseEnricher<XAiProjectDto, XAiProjectDto>
    {

        Task EnrichAsync(
            XAiProjectDto source,
            XAiProjectDto destination,
            bool fillConversations = true,
            bool fillConversationMessages = true,
            CancellationToken cancellationToken = default
        );

        Task EnrichRangeAsync(
            IEnumerable<XAiProjectDto> sources,
            IEnumerable<XAiProjectDto> destinations,
            bool fillConversations = true,
            bool fillConversationMessages = true,
            CancellationToken cancellationToken = default
        );        
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Entities\IXAiConversationEntityHub.cs

using System;
using xAiModels.Models.Entities;
using xPushService.Interfaces;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiConversationEntityHub : IXBaseEntityHub<XAiConversation, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Entities\IXAiConversationGraphQLTypeHelper.cs

using System;
using xAiModels.Models.Entities;
using xDataService.Interfaces;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiConversationGraphQLTypeHelper : IXBaseGraphQLTypeHelper<XAiConversation, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Entities\IXAiConversationKeyGenerator.cs

using System;
using xAiModels.Models.Entities;
using xDataService.Interfaces;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiConversationKeyGenerator : IXKeyGenerator<XAiConversation, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Entities\IXAiConversationRepository.cs

using System;
using xAiModels.Models.Entities;
using xDataService.Interfaces;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiConversationRepository : IXBaseRepository<XAiConversation, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Entities\IXAiConversationRepositoryEvents.cs

using xAiModels.Models.Entities;
using xDataService.Interfaces;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiConversationRepositoryEvents : IXBaseRepositoryEvents<XAiConversation>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Entities\IXAiConversationRepositoryProvider.cs

using System;
using xAiModels.Models.Entities;
using xAiModels.Providers.Entities;
using xPushService.Interfaces;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiConversationRepositoryProvider : IXBaseEntityProvider<XAiConversation, Guid, XAiConversationEntityHub>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Entities\IXAiConversationSeeder.cs

using System;
using xAiModels.Models.Entities;
using xDataService.Interfaces;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiConversationSeeder : IXBaseDbSeeder<XAiConversation, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Entities\IXAiMessageEntityHub.cs

using System;
using xAiModels.Models.Entities;
using xPushService.Interfaces;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiMessageEntityHub : IXBaseEntityHub<XAiMessage, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Entities\IXAiMessageGraphQLTypeHelper.cs

using System;
using xAiModels.Models.Entities;
using xDataService.Interfaces;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiMessageGraphQLTypeHelper : IXBaseGraphQLTypeHelper<XAiMessage, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Entities\IXAiMessageKeyGenerator.cs

using System;
using xAiModels.Models.Entities;
using xDataService.Interfaces;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiMessageKeyGenerator : IXKeyGenerator<XAiMessage, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Entities\IXAiMessageRepository.cs

using System;
using xAiModels.Models.Entities;
using xDataService.Interfaces;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiMessageRepository : IXBaseRepository<XAiMessage, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Entities\IXAiMessageRepositoryEvents.cs

using xAiModels.Models.Entities;
using xDataService.Interfaces;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiMessageRepositoryEvents : IXBaseRepositoryEvents<XAiMessage>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Entities\IXAiMessageRepositoryProvider.cs

using System;
using xAiModels.Models.Entities;
using xAiModels.Providers.Entities;
using xPushService.Interfaces;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiMessageRepositoryProvider : IXBaseEntityProvider<XAiMessage, Guid, XAiMessageEntityHub>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Entities\IXAiMessageSeeder.cs

using System;
using xAiModels.Models.Entities;
using xDataService.Interfaces;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiMessageSeeder : IXBaseDbSeeder<XAiMessage, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Entities\IXAiProjectEntityHub.cs

using System;
using xAiModels.Models.Entities;
using xPushService.Interfaces;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiProjectEntityHub : IXBaseEntityHub<XAiProject, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Entities\IXAiProjectGraphQLTypeHelper.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using xAiModels.Models.Entities;
using xDataService.Interfaces;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiProjectGraphQLTypeHelper : IXBaseGraphQLTypeHelper<XAiProject, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Entities\IXAiProjectKeyGenerator.cs

using System;
using xAiModels.Models.Entities;
using xDataService.Interfaces;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiProjectKeyGenerator : IXKeyGenerator<XAiProject, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Entities\IXAiProjectRepository.cs

using System;
using xAiModels.Models.Entities;
using xDataService.Interfaces;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiProjectRepository : IXBaseRepository<XAiProject, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Entities\IXAiProjectRepositoryEvents.cs

using xAiModels.Models.Entities;
using xDataService.Interfaces;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiProjectRepositoryEvents : IXBaseRepositoryEvents<XAiProject>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Entities\IXAiProjectRepositoryProvider.cs

using System;
using xAiModels.Models.Entities;
using xAiModels.Providers.Entities;
using xPushService.Interfaces;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiProjectRepositoryProvider : IXBaseEntityProvider<XAiProject, Guid, XAiProjectEntityHub>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Interfaces\Entities\IXAiProjectSeeder.cs

using System;
using xAiModels.Models.Entities;
using xDataService.Interfaces;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiProjectSeeder : IXBaseDbSeeder<XAiProject, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Models\XAiModelDescriptor.cs

using xAiModels.Constants;

namespace xAiModels.Models
{
    /// <summary>
    /// Describe a Provided Model ...
    /// </summary>
    public class XAiModelDescriptor
    {
        /// <summary>
        /// Provider Name ...
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// LLM Provider Url ...
        /// </summary>
        public string Url { get; set; }

        /// <summary>
        /// Model Name ...
        /// </summary>
        public string LLM { get; set; }

        /// <summary>
        /// Api Key for Connection ...
        /// </summary>
        public string ApiKey { get; set; }

        /// <summary>
        /// Specified Model Provider Type ...
        /// </summary>
        public XAiModelProviderType Provider { get; set; } = XAiModelProviderType.Ollama;        
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Models\Dtos\XAiConversationDto.cs

using System;
using System.Collections.Generic;
using xModels.Base;
using xModels.Dtos;

namespace xAiModels.Models.Dtos
{
    /// <summary>
    /// Conversation Describe ...
    /// </summary>
    public class XAiConversationDto : XBaseGuidIDEntityDto
    {
        /// <summary>
        /// User Identifier ...
        /// </summary>
        /// <value></value>
        public string OwnerId { get; set; }

        /// <summary>
        /// Owner Info ...
        /// </summary>
        public virtual XPersonDto Owner { get; set; } = null;

        /// <summary>
        /// Conversation Title Resource ID ...
        /// </summary>
        public string Title { get; set; }

        /// <summary>
        /// All Exists Title Locale Resources ...
        /// </summary>
        public virtual XResourceDto TitleResource { get; set; }

        /// <summary>
        /// Created Time ...
        /// </summary>
        public DateTime CreatedOn { get; set; }

        /// <summary>
        /// Updated Time ...
        /// </summary>
        public DateTime UpdatedAt { get; set; }

        /// <summary>
        /// Conversation Messages ...
        /// </summary>
        public virtual IEnumerable<XAiMessageDto> Messages { get; set; } = [];

        /// <summary>
        /// Project ID ...
        /// </summary>
        public Guid ProjectId { get; set; }

        /// <summary>
        /// Project Title Resource ID ...
        /// </summary>
        public virtual string ProjectTitle { get; set; } = null;

        /// <summary>
        /// Project Description Resource ID ...
        /// </summary>
        public virtual string ProjectDescription { get; set; } = null;

        /// <summary>
        /// Project Title Locale Resources ...
        /// </summary>
        public virtual XResourceDto ProjectTitleResource { get; set; }

        /// <summary>
        /// Project Description Locale Resources ...
        /// </summary>
        public virtual XResourceDto ProjectDescriptionResource { get; set; }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Models\Dtos\XAiMessageDto.cs

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using xAiModels.Constants;
using xCommons.Extensions;
using xModels.Base;
using xModels.Dtos;

namespace xAiModels.Models.Dtos
{
    public class XAiMessageDto : XBaseGuidIDEntityDto
    {
        /// <summary>
        /// User Identifier ...
        /// </summary>
        /// <value></value>
        public string OwnerId { get; set; }

        /// <summary>
        /// Owner Info ...
        /// </summary>
        public virtual XPersonDto Owner { get; set; } = null;

        /// <summary>
        /// Message of Chat ...
        /// </summary>
        public string Content { get; set; }

        /// <summary>
        /// Role of Message ...
        /// </summary>
        public XAiChatRole Role { get; set; }

        /// <summary>
        /// Created Time ...
        /// </summary>
        public DateTime CreatedOn { get; set; }

        /// <summary>
        /// Updated Time ...
        /// </summary>
        public DateTime UpdatedAt { get; set; }

        /// <summary>
        /// Conversation Id ...
        /// </summary>
        public Guid ConversationId { get; set; }

        /// <summary>
        /// Conversation Info ...
        /// </summary>
        public virtual string ConversationTitle { get; set; } = null;
        public virtual XResourceDto ConversationTitleResouce { get; set; } = null;

        //
        #region Meta Data Property ...
        private string _metadata;

        /// <summary>
        /// Meta Data of Message ...
        /// </summary>
        [NotMapped]
        public IDictionary<string, object> Metadata { get; private set; } = null;

        /// <summary>
        /// Meta Data of MEssage in Json ... 
        /// </summary>
        public string MeatDatas
        {
            //
            get => _metadata;

            //
            set
            {
                //
                // Converts Json String to Dictionary ...
                if (!value.IsNullOrEmpty())
                {
                    Metadata = value.FromJSON<IDictionary<string, object>>();
                }

                //
                _metadata = value;
            }
        }
        #endregion                
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Models\Dtos\XAiProjectDto.cs

using System;
using System.Collections.Generic;
using xModels.Base;
using xModels.Dtos;

namespace xAiModels.Models.Dtos
{
    /// <summary>
    /// Describe a Project ...
    /// </summary>
    public class XAiProjectDto : XBaseGuidIDEntityDto
    {
        /// <summary>
        /// User Identifier ...
        /// </summary>
        /// <value></value>
        public string OwnerId { get; set; }

        /// <summary>
        /// Owner Info ...
        /// </summary>
        public virtual XPersonDto Owner { get; set; } = null;

        /// <summary>
        /// Title of Project ...
        /// </summary>
        public string Title { get; set; }

        /// <summary>
        /// All Exists Title Locale Resources ...
        /// </summary>
        public virtual XResourceDto TitleResource { get; set; }

        /// <summary>
        /// Description of Project ...
        /// </summary>
        public string Description { get; set; }

        /// <summary>
        /// All Exists Description Locale Resources ...
        /// </summary>
        public virtual XResourceDto DescriptionResource { get; set; }

        /// <summary>
        /// a Prompt for Project Start ...
        /// </summary>
        public string Prompt { get; set; }

        /// <summary>
        /// Created Time ...
        /// </summary>
        public DateTime CreatedOn { get; set; }

        /// <summary>
        /// Updated Time ...
        /// </summary>
        public DateTime UpdatedAt { get; set; }

        /// <summary>
        /// Conversations ...
        /// </summary>
        public virtual IList<XAiConversationDto> Conversations { get; set; } = [];
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Models\Entities\XAiConversation.cs

using System;
using System.ComponentModel.DataAnnotations;
using xModels.Base;

namespace xAiModels.Models.Entities
{
    /// <summary>
    /// Conversation Describe ...
    /// </summary>
    public class XAiConversation : XBaseGuidIDEntity
    {
        /// <summary>
        /// User Identifier ...
        /// </summary>
        /// <value></value>
        [Required]
        [StringLength(255)]
        public string OwnerId { get; set; }

        /// <summary>
        /// Title of Conversation ...
        /// </summary>
        [Required]
        [StringLength(255)]
        public string Title { get; set; }

        /// <summary>
        /// Created Time ...
        /// </summary>
        public DateTime CreatedOn { get; set; }

        /// <summary>
        /// Updated Time ...
        /// </summary>
        public DateTime UpdatedAt { get; set; }

        /// <summary>
        /// Project ID ...
        /// </summary>
        [Required]
        public Guid ProjectId { get; set; }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Models\Entities\XAiMessage.cs

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using xAiModels.Constants;
using xCommons.Extensions;
using xModels.Base;

namespace xAiModels.Models.Entities
{
    /// <summary>
    /// Message Model of Ai ... 
    /// </summary>
    public class XAiMessage : XBaseGuidIDEntity
    {
        /// <summary>
        /// User Identifier ...
        /// </summary>
        /// <value></value>
        [Required]
        [StringLength(255)]
        public string OwnerId { get; set; }

        /// <summary>
        /// Message of Chat ...
        /// </summary>
        [Required]
        public string Content { get; set; }

        /// <summary>
        /// Role of Message ...
        /// </summary>
        public XAiChatRole Role { get; set; }

        /// <summary>
        /// Created Time ...
        /// </summary>
        public DateTime CreatedOn { get; set; }

        /// <summary>
        /// Updated Time ...
        /// </summary>
        public DateTime UpdatedAt { get; set; }

        /// <summary>
        /// Conversation Id ...
        /// </summary>
        [StringLength(255)]
        public Guid ConversationId { get; set; }

        //
        #region Meta Data Property ...
        private string _metadata;

        /// <summary>
        /// Meta Data of Message ...
        /// </summary>
        [NotMapped]
        public IDictionary<string, object> Metadata { get; private set; } = null;

        /// <summary>
        /// Meta Data of MEssage in Json ... 
        /// </summary>
        public string MeatDatas
        {
            //
            get => _metadata;

            //
            set
            {
                //
                // Converts Json String to Dictionary ...
                if (!value.IsNullOrEmpty())
                {
                    Metadata = value.FromJSON<IDictionary<string, object>>();
                }

                //
                _metadata = value;
            }
        }
        #endregion        
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Models\Entities\XAiProject.cs

using System;
using System.ComponentModel.DataAnnotations;
using xModels.Base;

namespace xAiModels.Models.Entities
{
    /// <summary>
    /// Describe a Project ...
    /// </summary>
    public class XAiProject : XBaseGuidIDEntity
    {
        /// <summary>
        /// User Identifier ...
        /// </summary>
        /// <value></value>
        [Required]
        [StringLength(255)]
        public string OwnerId { get; set; }

        /// <summary>
        /// Title of Project ...
        /// </summary>
        [Required]
        [StringLength(255)]
        public string Title { get; set; }

        /// <summary>
        /// Description of Project ...
        /// </summary>
        [StringLength(1000)]
        public string Description { get; set; }

        /// <summary>
        /// a Prompt for Project Start ...
        /// </summary>
        public string Prompt { get; set; }

        /// <summary>
        /// Created Time ...
        /// </summary>
        public DateTime CreatedOn { get; set; }

        /// <summary>
        /// Updated Time ...
        /// </summary>
        public DateTime UpdatedAt { get; set; }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\XAiConversationEntityRegisterar.cs

using System;
using Microsoft.EntityFrameworkCore;
using xAiModels.Configurations.Entities;
using xAiModels.Models.Entities;
using xDataService.Interfaces;
using xDataService.Providers;

namespace xAiModels.Providers
{
    public class XAiConversationEntityRegisterar : XBaseEntityRegisterar, IXEntityRegisterar
    {
        public XAiConversationEntityRegisterar()
        : base(nameof(XAiConversationEntityRegisterar))
        {
            //
            // Add Entity ...
            AddEntity<XAiConversation, Guid>();
        }

        /// <summary>
        /// Configure Entities ...
        /// </summary>
        /// <param name="modelBuilder"></param>
        public override void ConfigureEntities(ModelBuilder modelBuilder)
        {
            //
            // Configure Entity Using Provided ...
            modelBuilder
                .ApplyConfigurationsFromAssembly(typeof(XAiConversationEntityConfiguration).Assembly);

            //
            base.ConfigureEntities(modelBuilder);
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\XAiConversationTitleResourceProvider.cs

using xAiModels.Constants;
using xAiModels.Interfaces;
using xCommons.Configurations;
using xStringService.Interfaces.Dtos;
using xStringService.Providers;

namespace xAiModels.Providers
{
    public class XAiConversationTitleResourceProvider : XBaseOwnedItemResourceProvider, IXAiConversationTitleResourceProvider
    {
        public XAiConversationTitleResourceProvider(
            XAppConfiguration appConfig,
            IXStringServiceProvider stringProvider
        ) : base(
            appConfig:  appConfig,
            stringProvider: stringProvider,
            item: XAiModelsConstants.XAiConversationTitleIdentifier
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\XAiDataProvider.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
using MongoDB.Driver.Core.Connections;
using xAiModels.Constants;
using xAiModels.Extensions;
using xAiModels.Interfaces;
using xAiModels.Interfaces.Dtos;
using xAiModels.Models.Dtos;
using xAiModels.Providers.Hubs;
using xCommons.Configurations;
using xCommons.Extensions;
using xExceptions.Constants;
using xModels.Dtos;
using xStringService.Interfaces;

namespace xAiModels.Providers
{
    /// <summary>
    /// All Data Manipulation Requirements ...
    /// </summary>
    public class XAiDataProvider : IXAiDataProvider
    {
        //
        #region Properties ...
        private readonly IXStringProvider stringProvider;
        private readonly XAppConfiguration appConfiguration;
        private readonly IHubContext<XAiDataProviderHub> hub;
        private readonly IXAiMessageRepositoryService messageRepositoryService;
        private readonly IXAiProjectRepositoryService projectRepositoryService;
        private readonly IXAiProjectTitleResourceProvider projectTitleResourceProvider;
        private readonly IXAiConversationRepositoryService conversationRepositoryService;
        private readonly IXAiConversationTitleResourceProvider conversationTitleResourceProvider;
        private readonly IXAiProjectDescriptionResourceProvider projectDescriptionResourceProvider;
        #endregion

        public XAiDataProvider(
            IXStringProvider stringProvider,
            XAppConfiguration appConfiguration,
            IHubContext<XAiDataProviderHub> hub,
            IXAiMessageRepositoryService messageRepositoryService,
            IXAiProjectRepositoryService projectRepositoryService,
            IXAiProjectTitleResourceProvider projectTitleResourceProvider,
            IXAiConversationRepositoryService conversationRepositoryService,
            IXAiConversationTitleResourceProvider conversationTitleResourceProvider,
            IXAiProjectDescriptionResourceProvider projectDescriptionResourceProvider
        )
        {
            this.hub = hub;
            this.stringProvider = stringProvider;
            this.appConfiguration = appConfiguration;
            this.messageRepositoryService = messageRepositoryService;
            this.projectRepositoryService = projectRepositoryService;
            this.projectTitleResourceProvider = projectTitleResourceProvider;
            this.conversationRepositoryService = conversationRepositoryService;
            this.conversationTitleResourceProvider = conversationTitleResourceProvider;
            this.projectDescriptionResourceProvider = projectDescriptionResourceProvider;
        }

        //
        #region Loaders ...
        /// <summary>
        /// Load Projects List of Specified Owner ...
        /// </summary>
        /// <param name="ownerId"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<IEnumerable<XAiProjectDto>> LoadProjects(
            string ownerId,
            bool fillConversations = false,
            bool fillConversationMessages = false,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Validate Args ...
            if (ownerId.IsNullOrEmpty())
            {
                XException.InvalidArgs.Throw();
            }

            //
            // Handle Default Project and Conversation ...
            bool hasDefault = await HasDefaultProject(
                ownerId: ownerId,
                cancellationToken: cancellationToken
            );
            if (!hasDefault)
            {
                //
                await CreateDefaultProject(
                    ownerId: ownerId,
                    connectionId: null,
                    cancellationToken: cancellationToken
                );
            }

            //
            // Retrieve Project List ...
            var result = await projectRepositoryService.FindManyAsync(
                includeBuilder: null,
                ignoreSoftDeleteds: true,
                fillConversations: fillConversations,
                predicate: x => x.OwnerId == ownerId,
                cancellationToken: cancellationToken,
                orderBuilder: x => x.OrderBy(y => y.CreatedOn),
                fillConversationMessages: fillConversationMessages
            );

            //
            return result;
        }

        /// <summary>
        /// Load List of Conversations for Specified Project ... 
        /// </summary>
        /// <param name="projectId"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<IEnumerable<XAiConversationDto>> LoadConversations(
            Guid projectId,
            bool fillMessages = false,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Validate Args ...
            if (projectId.IsNull() ||
                projectId.IsDefaultGuid()
            )
            {
                XException.InvalidArgs.Throw();
            }

            //
            // Retrieve Conversation's List ...
            var result = await conversationRepositoryService.FindManyAsync(
                includeBuilder: null,
                ignoreSoftDeleteds: false,
                fillMessages: fillMessages,
                cancellationToken: cancellationToken,
                predicate: x => x.ProjectId == projectId,
                orderBuilder: x => x.OrderBy(y => y.CreatedOn)
            );

            //
            return result;
        }

        /// <summary>
        /// Load Messages of Specified Conversation ...
        /// </summary>
        /// <param name="conversationId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<IEnumerable<XAiMessageDto>> LoadMessages(
            Guid conversationId,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Validate Args ...
            if (conversationId.IsNull() ||
                conversationId.IsDefaultGuid()
            )
            {
                XException.InvalidArgs.Throw();
            }

            //
            // Retrieve Message Lists of Specified Conversation ...
            var result = await messageRepositoryService.FindManyAsync(
                includeBuilder: null,
                ignoreSoftDeleteds: true,
                cancellationToken: cancellationToken,
                orderBuilder: x => x.OrderBy(y => y.CreatedOn),
                predicate: x => x.ConversationId == conversationId
            );

            //
            return result;
        }
        #endregion

        //
        #region Creator/Adders ...
        public async Task<XAiProjectDto> CreateProject(
            string ownerId,
            XAiProjectDto item = null,
            string connectionId = null,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Validate Args ...
            if (ownerId.IsNullOrEmpty())
            {
                XException.InvalidArgs.Throw();
            }

            //
            // Check Project Provided or not ...
            var hasProject = !item.IsNullOrDefault();

            //
            // Normalize Provided Project ...
            if (hasProject)
            {
                //
                item.CreatedOn = DateTime.UtcNow;

                //
                // Validate Item ...
                if (!item.IsValidForAddOrUpdate())
                {
                    XException.InvalidData.Throw();
                }
            }

            //
            XAiProjectDto result = null;

            //
            // Create Provided Project ...
            if (hasProject)
            {
                //
                result = await projectRepositoryService.AddAsync(
                    item: item,
                    saveChanges: true,
                    fillConversations: false,
                    fillConversationMessages: false,
                    cancellationToken: cancellationToken
                );

                //
                // Handling Send Push Notification ...
                if (!result.IsNullOrDefault() &&
                    !connectionId.IsNullOrEmpty()
                )
                {
                    //
                    await SendPush(
                        connectionId: connectionId,
                        cancellationToken: cancellationToken,
                        payLoad: item.ToJSON(camelCase: true),
                        action: XAiDataProviderHubActions.ProjectCreated.GetStringValue()
                    );
                }
            }
            //
            // Check Default Project ...
            else
            {
                //
                // Check Default Project Exists or not ...
                var hasDefault = await HasDefaultProject(
                    ownerId: ownerId,
                    cancellationToken: cancellationToken
                );

                //
                // If Default Project Exists, Throw Exception ...
                if (hasDefault)
                {
                    XException.InvalidArgs.Throw();
                }

                //
                // Create and Retrive Default Project ...
                result = await GetDefaultProject(
                    ownerId: ownerId,
                    connectionId: connectionId,
                    cancellationToken: cancellationToken
                );
            }

            //
            // Validate ...
            if (result.IsNullOrDefault())
            {
                XException.ActionFailed.Throw();
            }

            //
            return result;
        }

        public async Task<XAiConversationDto> CreateConversation(
            Guid projectId,
            XAiConversationDto item = null,
            string connectionId = null,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Validate Args ...
            if (projectId.IsNull() ||
                projectId.IsDefaultGuid())
            {
                XException.InvalidArgs.Throw();
            }

            //
            // Handle Proejct ...
            XAiProjectDto project = await projectRepositoryService.GetAsync(
                id: projectId,
                includeBuilder: null,
                ignoreSoftDeleteds: true,
                fillConversations: false,
                fillConversationMessages: false,
                cancellationToken: cancellationToken
            );
            if (project.IsNullOrDefault())
            {
                XException.NotFound.Throw();
            }

            //
            // Check Conversation Provided or not ...
            var hasConversation = !item.IsNullOrDefault();

            //
            // Normalize Conversation if Provided ...
            if (hasConversation)
            {
                //
                item.ProjectId = project.Id;
                item.CreatedOn = DateTime.UtcNow;

                //
                // Validate Item ...
                if (!item.IsValidForAddOrUpdate())
                {
                    XException.InvalidData.Throw();
                }
            }

            //
            XAiConversationDto result = null;

            //
            // Create Provided Conversation ...
            if (hasConversation)
            {
                //
                result = await conversationRepositoryService.AddAsync(
                    item: item,
                    saveChanges: true,
                    fillMessages: false,
                    cancellationToken: cancellationToken
                );

                //
                // Handling Send Push Notification ...
                if (!result.IsNullOrDefault() &&
                    !connectionId.IsNullOrEmpty()
                )
                {
                    //
                    await SendPush(
                        connectionId: connectionId,
                        cancellationToken: cancellationToken,
                        payLoad: item.ToJSON(camelCase: true),
                        action: XAiDataProviderHubActions.ConversationCreated.GetStringValue()
                    );
                }
            }
            //
            // Check Default Conversation ...
            else
            {
                //
                // Check Default Conversation Exists or not ...
                var hasDefault = await HasDefaultConversation(
                    projectId: project.Id,
                    ownerId: project.OwnerId,
                    cancellationToken: cancellationToken
                );

                //
                // If Default Project Exists, Throw Exception ...
                if (hasDefault)
                {
                    XException.InvalidArgs.Throw();
                }

                //
                // Create and Retrive Default Conversation ...
                result = await GetDefaultConversation(
                    projectId: project.Id,
                    ownerId: project.OwnerId,
                    connectionId: connectionId,
                    cancellationToken: cancellationToken
                );
            }

            //
            // Validate ...
            if (result.IsNullOrDefault())
            {
                XException.ActionFailed.Throw();
            }

            //
            return result;
        }

        public async Task<XAiMessageDto> AddMessage(
            Guid conversationId,
            XAiMessageDto item,
            string connectionId = null,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Validate Args ...
            if (item.IsNullOrDefault() ||
                conversationId.IsNull() ||
                conversationId.IsDefaultGuid()
            )
            {
                XException.InvalidArgs.Throw();
            }

            //
            // Normalize ...
            item.CreatedOn = DateTime.UtcNow;
            item.ConversationId = conversationId;

            //
            // Validate Item ...
            if (!item.IsValidForAddOrUpdate())
            {
                XException.InvalidData.Throw();
            }

            //
            var result = await messageRepositoryService.AddAsync(
                item: item,
                saveChanges: true,
                cancellationToken: cancellationToken
            );

            //
            // Handling Send Push Notification ...
            if (!result.IsNullOrDefault() &&
                !connectionId.IsNullOrEmpty()
            )
            {
                //
                await SendPush(
                    connectionId: connectionId,
                    cancellationToken: cancellationToken,
                    payLoad: item.ToJSON(camelCase: true),
                    action: XAiDataProviderHubActions.MessageCreated.GetStringValue()
                );
            }

            //
            return result;
        }
        #endregion

        //
        #region Updaters ...
        #endregion

        //
        #region Private ...
        /// <summary>
        /// Check Default Project Exists or not ...
        /// </summary>
        /// <param name="ownerId"></param>
        /// <param name="connectionId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        private async Task<bool> HasDefaultProject(
            string ownerId,
            string connectionId = null,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Validate Args ...
            var result = !ownerId.IsNullOrEmpty();
            if (!result)
            {
                return result;
            }

            //
            // Check Default Owner Exists or not ...
            result = !(await stringProvider.FindOne(
                cancellationToken: cancellationToken,
                predicate: x =>
                    x.ResourceTitle
                        .ToNormalString()
                        .Contains(ownerId.ToNormalString()) &&
                    x.ResourceTitle
                        .ToNormalString()
                        .Contains(XAiModelsConstants.XAiProjectTitleIdentifier
                            .ToNormalString()) &&
                    x.TranslatedValue
                    .ToNormalString()
                    .Contains(XAiDataProviderConstants.XDefaultAiProjectSuffix)
            ))
            .IsNullOrDefault();
            return result;
        }

        /// <summary>
        /// Retrieve Default Project of Specified Owner ...
        /// Create it if not Handled ...
        /// </summary>
        /// <param name="ownerId"></param>
        /// <param name="connectionId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        private async Task<XAiProjectDto> GetDefaultProject(
            string ownerId,
            string connectionId = null,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Check Default Project Exists or noit ...
            var isExists = await HasDefaultProject(
                ownerId: ownerId,
                connectionId: connectionId,
                cancellationToken: cancellationToken
            );
            if (!isExists)
            {
                //
                await CreateDefaultProject(
                    ownerId: ownerId,
                    connectionId: connectionId,
                    cancellationToken: cancellationToken
                );
            }

            //
            var resource = await stringProvider.FindOne(
                cancellationToken: cancellationToken,
                predicate: x =>
                    x.ResourceTitle
                        .ToNormalString()
                        .Contains(ownerId.ToNormalString()) &&
                    x.ResourceTitle
                        .ToNormalString()
                        .Contains(XAiModelsConstants.XAiProjectTitleIdentifier
                            .ToNormalString()) &&
                    x.TranslatedValue
                    .ToNormalString()
                    .Contains(XAiDataProviderConstants.XDefaultAiProjectSuffix)
            );
            if (resource.IsNullOrDefault())
            {
                XException.NotFound.Throw();
            }

            //
            var result = await projectRepositoryService.FindOneAsync(
                cancellationToken: cancellationToken,
                predicate: x => x.Title
                    .Equals(
                        resource.ResourceTitle,
                        StringComparison.InvariantCultureIgnoreCase
                    )
            );
            if (result.IsNullOrDefault())
            {
                XException.NotFound.Throw();
            }

            //
            return result;
        }

        /// <summary>
        /// Create Default Project for Specified Owner ... 
        /// </summary>
        /// <param name="ownerId"></param>
        /// <param name="connectionId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        private async Task CreateDefaultProject(
            string ownerId,
            string connectionId = null,
            CancellationToken cancellationToken = default
        )
        {
            //
            if (ownerId.IsNullOrEmpty())
            {
                return;
            }

            //
            var defaultLang = appConfiguration.DefaultLanguage;

            //
            // Check Default Owner Exists or not ...
            var isExists = await HasDefaultProject(
                ownerId: ownerId,
                cancellationToken: cancellationToken
            );
            if (isExists)
            {
                return;
            }

            //
            // Create Default ...
            var item = new XAiProjectDto()
            {
                OwnerId = ownerId,
                CreatedOn = DateTime.UtcNow,
                Title = Guid.NewGuid().ToString(),
                Description = Guid.NewGuid().ToString(),
            };
            item = await projectRepositoryService.AddAsync(
                item: item,
                saveChanges: true,
                cancellationToken: cancellationToken
            );

            //
            // Prepare Tile and Description Resource Ids ...
            var titleResource = await projectTitleResourceProvider.AddOrUpdateLocale(
                owner: ownerId,
                identifier: item.Id,
                locale: new XLocaleResourceDto
                {
                    Language = defaultLang,
                    Value = XAiDataProviderConstants.XDefaultAiProjectSuffix
                },
                cancellationToken: cancellationToken
            );
            var descriptionResource = await projectDescriptionResourceProvider.AddOrUpdateLocale(
                owner: ownerId,
                identifier: item.Id,
                new XLocaleResourceDto
                {
                    Language = defaultLang,
                    Value = XAiDataProviderConstants.XDefaultAiProjectSuffix
                },
                cancellationToken: cancellationToken
            );
            if (titleResource.IsNullOrDefault() ||
                descriptionResource.IsNullOrDefault()
            )
            {
                return;
            }

            //
            // Update Project Resources ...
            item.Title = titleResource.Resource;
            item.Description = descriptionResource.Resource;
            item = await projectRepositoryService.UpdateAsync(
                id: item.Id,
                item: item,
                saveChanges: true,
                cancellationToken: cancellationToken
            );

            //
            // Sending Push ...
            if (!connectionId.IsNullOrEmpty())
            {
                //
                await SendPush(
                    connectionId: connectionId,
                    cancellationToken: cancellationToken,
                    payLoad: item.ToJSON(camelCase: true),
                    action: XAiDataProviderHubActions.ProjectCreated.GetStringValue()
                );
            }
        }

        /// <summary>
        /// Check Default Conversation Exists or not ...
        /// </summary>
        /// <param name="ownerId"></param>
        /// <param name="projectId"></param>
        /// <param name="connectionId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        private async Task<bool> HasDefaultConversation(
            string ownerId,
            Guid projectId,
            string connectionId = null,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Validate Args ...
            var result = !ownerId.IsNullOrEmpty();
            if (!result)
            {
                return result;
            }

            //
            // Prepare Project ...
            XAiProjectDto project = null;
            if (projectId.IsNull() ||
                projectId.IsDefaultGuid()
            )
            {
                //
                project = await GetDefaultProject(
                    ownerId: ownerId,
                    connectionId: connectionId,
                    cancellationToken: cancellationToken
                );
            }
            else
            {
                //
                project = await projectRepositoryService.GetAsync(
                    id: project.Id,
                    includeBuilder: null,
                    ignoreSoftDeleteds: true,
                    fillConversations: false,
                    fillConversationMessages: false,
                    cancellationToken: cancellationToken
                );
            }
            if (project.IsNullOrDefault())
            {
                XException.NotFound.Throw();
            }

            //
            // Check Default Owner Exists or not ...
            var resource = await stringProvider.FindOne(
                cancellationToken: cancellationToken,
                predicate: x =>
                    x.ResourceTitle
                        .ToNormalString()
                        .Contains(ownerId.ToNormalString()) &&
                    x.ResourceTitle
                        .ToNormalString()
                        .Contains(XAiModelsConstants.XAiConversationTitleIdentifier
                            .ToNormalString()) &&
                    x.TranslatedValue
                    .ToNormalString()
                    .Contains(XAiDataProviderConstants.XDefaultAiConversationSuffix)
            );
            result = !resource.IsNullOrDefault();
            if (!result)
            {
                return result;
            }

            //
            result = !(await conversationRepositoryService.FindOneAsync(
                cancellationToken: cancellationToken,
                predicate: x =>
                    x.ProjectId == projectId &&
                    x.Title == resource.ResourceTitle
            ))
            .IsNullOrDefault();

            //
            return result;
        }

        /// <summary>
        /// Check Default Conversation Exists or not ...
        /// </summary>
        /// <param name="ownerId"></param>
        /// <param name="projectId"></param>
        /// <param name="connectionId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        private async Task CreateDefaultConversation(
            string ownerId,
            Guid projectId,
            string connectionId = null,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Validate Args ...
            var isValid = !ownerId.IsNullOrEmpty();
            if (!isValid)
            {
                return;
            }

            //
            var defaultLang = appConfiguration.DefaultLanguage;

            //
            // Prepare Project ...
            XAiProjectDto project = null;
            if (projectId.IsNull() ||
                projectId.IsDefaultGuid()
            )
            {
                //
                project = await GetDefaultProject(
                    ownerId: ownerId,
                    connectionId: connectionId,
                    cancellationToken: cancellationToken
                );
            }
            else
            {
                //
                project = await projectRepositoryService.GetAsync(
                    id: project.Id,
                    includeBuilder: null,
                    ignoreSoftDeleteds: true,
                    fillConversations: false,
                    fillConversationMessages: false,
                    cancellationToken: cancellationToken
                );
            }
            if (project.IsNullOrDefault())
            {
                XException.NotFound.Throw();
            }

            //
            // Check Default Owner Exists or not ...
            isValid = await HasDefaultConversation(
                ownerId: ownerId,
                projectId: project.Id,
                cancellationToken: cancellationToken
            );
            if (isValid)
            {
                return;
            }

            //
            // Create Default Conversation ...
            var item = new XAiConversationDto
            {
                OwnerId = ownerId,
                ProjectId = project.Id,
                CreatedOn = DateTime.UtcNow,
                Title = Guid.NewGuid().ToString()
            };
            item = await conversationRepositoryService.AddAsync(
                item: item,
                saveChanges: true,
                cancellationToken: cancellationToken
            );

            //
            // Prepare Title Resource Ids ...
            var titleResource = await conversationTitleResourceProvider.AddOrUpdateLocale(
                owner: ownerId,
                identifier: item.Id,
                locale: new XLocaleResourceDto
                {
                    Language = defaultLang,
                    Value = XAiDataProviderConstants.XDefaultAiConversationSuffix
                },
                cancellationToken: cancellationToken
            );
            if (titleResource.IsNullOrDefault())
            {
                return;
            }

            //
            // Update Conversation Resources ...
            item.Title = titleResource.Resource;
            item = await conversationRepositoryService.UpdateAsync(
                item: item,
                id: item.Id,
                saveChanges: true,
                cancellationToken: cancellationToken
            );

            //
            // Sending Push ...
            if (!connectionId.IsNullOrEmpty())
            {
                //
                await SendPush(
                    connectionId: connectionId,
                    cancellationToken: cancellationToken,
                    payLoad: item.ToJSON(camelCase: true),
                    action: XAiDataProviderHubActions.ConversationCreated.GetStringValue()
                );
            }
        }

        /// <summary>
        /// Retrieve Default Conversation of Specified Owner ...
        /// Create it if not Handled ...
        /// </summary>
        /// <param name="ownerId"></param>
        /// <param name="projectId"></param>
        /// <param name="connectionId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        private async Task<XAiConversationDto> GetDefaultConversation(
            string ownerId,
            Guid projectId,
            string connectionId = null,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Validate Args ...
            if (ownerId.IsNullOrEmpty())
            {
                XException.InvalidArgs.Throw();
            }

            //
            // Prepare Project ...
            XAiProjectDto project = null;
            if (projectId.IsNull() ||
                projectId.IsDefaultGuid()
            )
            {
                //
                project = await GetDefaultProject(
                    ownerId: ownerId,
                    connectionId: connectionId,
                    cancellationToken: cancellationToken
                );
            }
            else
            {
                //
                project = await projectRepositoryService.GetAsync(
                    id: project.Id,
                    includeBuilder: null,
                    ignoreSoftDeleteds: true,
                    fillConversations: false,
                    fillConversationMessages: false,
                    cancellationToken: cancellationToken
                );
            }
            if (project.IsNullOrDefault())
            {
                XException.NotFound.Throw();
            }

            //
            // Check Default Conversation Exists or noit ...
            var isExists = await HasDefaultConversation(
                ownerId: ownerId,
                projectId: project.Id,
                connectionId: connectionId,
                cancellationToken: cancellationToken
            );
            if (!isExists)
            {
                //
                await CreateDefaultConversation(
                    ownerId: ownerId,
                    projectId: project.Id,
                    connectionId: connectionId,
                    cancellationToken: cancellationToken
                );
            }

            //
            // Detect Conversation Resource ...
            var resource = await stringProvider.FindOne(
                cancellationToken: cancellationToken,
                predicate: x =>
                    x.ResourceTitle
                        .ToNormalString()
                        .Contains(ownerId.ToNormalString()) &&
                    x.ResourceTitle
                        .ToNormalString()
                        .Contains(XAiModelsConstants.XAiProjectTitleIdentifier
                            .ToNormalString()) &&
                    x.TranslatedValue
                    .ToNormalString()
                    .Contains(XAiDataProviderConstants.XDefaultAiProjectSuffix)
            );
            if (resource.IsNullOrDefault())
            {
                XException.NotFound.Throw();
            }

            //
            // Find Default Conversation ...
            var result = await conversationRepositoryService.FindOneAsync(
                cancellationToken: cancellationToken,
                predicate: x => x.Title
                    .Equals(
                        resource.ResourceTitle,
                        StringComparison.InvariantCultureIgnoreCase
                    ) &&
                    x.ProjectId == project.Id
            );
            if (result.IsNullOrDefault())
            {
                XException.NotFound.Throw();
            }

            //
            return result;
        }

        /// <summary>
        /// Normalize and Validate Language ...
        /// </summary>
        /// <param name="language"></param>
        /// <returns></returns>
        private string NormalizeAndValidateLanguage(string language)
        {
            //
            if (language.IsNullOrEmpty())
            {
                language = appConfiguration.DefaultLanguage;
            }

            //
            if (language.IsNullOrEmpty())
            {
                XException.InvalidArgs.Throw();
            }

            //
            return language;
        }

        /// <summary>
        /// Send Custom Push Message ...
        /// </summary>
        /// <param name="action"></param>
        /// <param name="payLoad"></param>
        /// <param name="connectionId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task SendPush(
            string action,
            string payLoad,
            string connectionId = null,
            CancellationToken cancellationToken = default
        )
        {
            //
            try
            {
                //
                // Collect Allowed Actions ...
                var actions = new List<string>
                {
                    XAiDataProviderHubActions.ProjectCreated.GetStringValue(),
                    XAiDataProviderHubActions.ProjectUpdated.GetStringValue(),
                    XAiDataProviderHubActions.ProjectRemoved.GetStringValue(),
                    XAiDataProviderHubActions.MessageCreated.GetStringValue(),
                    XAiDataProviderHubActions.MessageUpdated.GetStringValue(),
                    XAiDataProviderHubActions.MessageRemoved.GetStringValue(),
                    XAiDataProviderHubActions.ConversationCreated.GetStringValue(),
                    XAiDataProviderHubActions.ConversationUpdated.GetStringValue(),
                    XAiDataProviderHubActions.ConversationRemoved.GetStringValue(),
                };

                //
                // Validate Args ...
                var isValid =
                    !action.IsNullOrEmpty() &&
                    actions.Contains(action);
                if (!isValid)
                {
                    return;
                }

                //
                // Retrieve Connection Id ...
                var clients = hub.Clients.All;
                if (!connectionId.IsNullOrEmpty())
                {
                    clients = hub.Clients.AllExcept(connectionId);
                }

                //
                await clients.SendAsync(
                    action,
                    payLoad,
                    connectionId,
                    cancellationToken
                );
            }
            catch { }
        }
        #endregion
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\XAiMessageEntityRegisterar.cs

using System;
using Microsoft.EntityFrameworkCore;
using xAiModels.Configurations.Entities;
using xAiModels.Models.Entities;
using xDataService.Interfaces;
using xDataService.Providers;

namespace xAiModels.Providers
{
    public class XAiMessageEntityRegisterar : XBaseEntityRegisterar, IXEntityRegisterar
    {
        public XAiMessageEntityRegisterar()
        : base(nameof(XAiMessageEntityRegisterar))
        {
            //
            // Add Entity ...
            AddEntity<XAiMessage, Guid>();
        }

        /// <summary>
        /// Configure Entities ...
        /// </summary>
        /// <param name="modelBuilder"></param>
        public override void ConfigureEntities(ModelBuilder modelBuilder)
        {
            //
            // Configure Entity Using Provided ...
            modelBuilder
                .ApplyConfigurationsFromAssembly(typeof(XAiMessageEntityConfiguration).Assembly);

            //
            base.ConfigureEntities(modelBuilder);
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\XAiProjectDescriptionResourceProvider.cs

using xAiModels.Constants;
using xAiModels.Interfaces;
using xCommons.Configurations;
using xStringService.Interfaces.Dtos;
using xStringService.Providers;

namespace xAiModels.Providers
{
    public class XAiProjectDescriptionResourceProvider : XBaseOwnedItemResourceProvider, IXAiProjectDescriptionResourceProvider
    {
        public XAiProjectDescriptionResourceProvider(
            XAppConfiguration appConfig,
            IXStringServiceProvider stringProvider
        ) : base(
            appConfig: appConfig,
            stringProvider: stringProvider,
            item: XAiModelsConstants.XAiProjectDescriptionIdentifier
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\XAiProjectEntityRegisterar.cs

using System;
using Microsoft.EntityFrameworkCore;
using xAiModels.Configurations.Entities;
using xAiModels.Models.Entities;
using xDataService.Interfaces;
using xDataService.Providers;

namespace xAiModels.Providers
{
    public class XAiProjectEntityRegisterar : XBaseEntityRegisterar, IXEntityRegisterar
    {
        public XAiProjectEntityRegisterar()
        : base(nameof(XAiProjectEntityRegisterar))
        {
            //
            // Add Entity ...
            AddEntity<XAiProject, Guid>();
        }

        /// <summary>
        /// Configure Entities ...
        /// </summary>
        /// <param name="modelBuilder"></param>
        public override void ConfigureEntities(ModelBuilder modelBuilder)
        {
            //
            // Configure Entity Using Provided ...
            modelBuilder
                .ApplyConfigurationsFromAssembly(typeof(XAiProjectEntityConfiguration).Assembly);

            //
            base.ConfigureEntities(modelBuilder);
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\XAiProjectTitleResourceProvider.cs

using xAiModels.Constants;
using xAiModels.Interfaces;
using xCommons.Configurations;
using xStringService.Interfaces.Dtos;
using xStringService.Providers;

namespace xAiModels.Providers
{
    public class XAiProjectTitleResourceProvider : XBaseOwnedItemResourceProvider, IXAiProjectTitleResourceProvider
    {
        public XAiProjectTitleResourceProvider(
            XAppConfiguration appConfig,
            IXStringServiceProvider stringProvider
        ) : base(
            appConfig: appConfig,
            stringProvider: stringProvider,
            item: XAiModelsConstants.XAiProjectTitleIdentifier
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Dtos\XAiConversationDtoHub.cs

using System;
using Microsoft.Extensions.Logging;
using xAiModels.Interfaces.Dtos;
using xAiModels.Models.Dtos;
using xAiModels.Models.Entities;
using xPushService.Base;

namespace xAiModels.Providers.Dtos
{
    public class XAiConversationDtoHub : XBaseDtoHub<XAiConversation, XAiConversationDto, Guid>, IXAiConversationDtoHub
    {
        public XAiConversationDtoHub(
            ILogger<XAiConversationDtoHub> logger
        ) : base(logger)
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Dtos\XAiConversationRepositoryService.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Query;
using xAiModels.Configurations;
using xAiModels.Interfaces.Dtos;
using xAiModels.Interfaces.Enrichers;
using xAiModels.Interfaces.Entities;
using xAiModels.Models.Dtos;
using xAiModels.Models.Entities;
using xCommons.Extensions;
using xDataService.Providers;
using xModels.Dtos;

namespace xAiModels.Providers.Dtos
{
    public class XAiConversationRepositoryService : XBaseRepositoryService<XAiConversation, XAiConversationDto, Guid>, IXAiConversationRepositoryService
    {
        private readonly IXAiConversationEnricher enricher;

        public XAiConversationRepositoryService(
            IXAiConversationEnricher enricher,
            IXAiConversationRepository repository
        ) : base(
            repository,
            mapperProfiles:
            [
                new XAiModelMappersProfile()
            ]
        )
        {
            this.enricher = enricher;
        }

        //
        #region Actions ...
        //
        #region Add ...
        /// <summary>
        /// add a new Dto ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="saveChanges"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task<XAiConversationDto> AddAsync(
            XAiConversationDto item,
            bool saveChanges = true,
            CancellationToken cancellationToken = default
        )
        {
            return await AddAsync(
                item: item,
                fillMessages: false,
                saveChanges: saveChanges,
                cancellationToken: cancellationToken
            );
        }

        /// <summary>
        /// add or update a Dto (add if not exists/update if exists) ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="saveChanges"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task<XAiConversationDto> AddOrUpdateAsync(
            XAiConversationDto item,
            bool saveChanges = true,
            CancellationToken cancellationToken = default
        )
        {
            return await AddOrUpdateAsync(
                item: item,
                fillMessages: false,
                saveChanges: saveChanges,
                cancellationToken: cancellationToken
            );
        }
        #endregion

        //
        #region Update ...
        /// <summary>
        /// Update an Dto values ...
        /// </summary>
        /// <param name="id"></param>
        /// <param name="item"></param>
        /// <param name="saveChanges"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task<XAiConversationDto> UpdateAsync(
            Guid id,
            XAiConversationDto item,
            bool saveChanges = true,
            CancellationToken cancellationToken = default
        )
        {
            return await UpdateAsync(
                id: id,
                item: item,
                fillMessages: false,
                saveChanges: saveChanges,
                cancellationToken: cancellationToken
            );
        }
        #endregion

        //
        #region Remove ...
        /// <summary>
        /// remove an Dto by it's Id ...
        /// </summary>
        /// <param name="id"></param>
        /// <param name="softDelete"></param>
        /// <param name="saveChanges"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task<XAiConversationDto> RemoveAsync(
            Guid id,
            bool softDelete = true,
            bool saveChanges = true,
            CancellationToken cancellationToken = default
        )
        {
            return await RemoveAsync(
                id: id,
                fillMessages: false,
                softDelete: softDelete,
                saveChanges: saveChanges,
                cancellationToken: cancellationToken
            );
        }

        /// <summary>
        /// remove an Dto ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="softDelete"></param>
        /// <param name="saveChanges"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task<XAiConversationDto> RemoveAsync(
            XAiConversationDto item,
            bool softDelete = true,
            bool saveChanges = true,
            CancellationToken cancellationToken = default
        )
        {
            return await RemoveAsync(
                item: item,
                fillMessages: false,
                softDelete: softDelete,
                saveChanges: saveChanges,
                cancellationToken: cancellationToken
            );
        }
        #endregion

        //
        #region Retrieve ...
        /// <summary>
        /// retrieve an Dto by it's Id ...
        /// </summary>
        /// <param name="id"></param>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task<XAiConversationDto> GetAsync(
            Guid id,
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiConversation>, IIncludableQueryable<XAiConversation, object>> includeBuilder = null,
            CancellationToken cancellationToken = default
        )
        {
            return await GetAsync(
                id: id,
                fillMessages: false,
                includeBuilder: includeBuilder,
                cancellationToken: cancellationToken,
                ignoreSoftDeleteds: ignoreSoftDeleteds
            );
        }

        /// <summary>
        /// retrieve all exists Dtos ...
        /// </summary>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="orderBuilder"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task<IEnumerable<XAiConversationDto>> GetAllAsync(
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiConversation>, IOrderedQueryable<XAiConversation>> orderBuilder = null,
            Func<IQueryable<XAiConversation>, IIncludableQueryable<XAiConversation, object>> includeBuilder = null,
            CancellationToken cancellationToken = default
        )
        {
            return await GetAllAsync(
                fillMessages: false,
                orderBuilder: orderBuilder,
                includeBuilder: includeBuilder,
                cancellationToken: cancellationToken,
                ignoreSoftDeleteds: ignoreSoftDeleteds
            );
        }

        /// <summary>
        /// retrieve all exists Dtos
        /// as Async Enumerable ...
        /// </summary>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="orderBuilder"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override IAsyncEnumerable<XAiConversationDto> GetAllAsAsyncEnumerable(
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiConversation>, IOrderedQueryable<XAiConversation>> orderBuilder = null,
            Func<IQueryable<XAiConversation>, IIncludableQueryable<XAiConversation, object>> includeBuilder = null,
            CancellationToken cancellationToken = default
        )
        {
            return GetAllAsAsyncEnumerable(
                fillMessages: false,
                orderBuilder: orderBuilder,
                includeBuilder: includeBuilder,
                cancellationToken: cancellationToken,
                ignoreSoftDeleteds: ignoreSoftDeleteds
            );
        }

        /// <summary>
        /// find an Dto by providing a Conditional Expression ...
        /// </summary>
        /// <param name="predicate"></param>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task<XAiConversationDto> FindOneAsync(
            Expression<Func<XAiConversation, bool>> predicate,
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiConversation>, IIncludableQueryable<XAiConversation, object>> includeBuilder = null,
            CancellationToken cancellationToken = default
        )
        {
            return await FindOneAsync(
                fillMessages: false,
                predicate: predicate,
                includeBuilder: includeBuilder,
                cancellationToken: cancellationToken,
                ignoreSoftDeleteds: ignoreSoftDeleteds
            );
        }

        /// <summary>
        /// find a collection of Dtos by proving a Conditional Expression ...
        /// </summary>
        /// <param name="predicate"></param>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="orderBuilder"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task<IEnumerable<XAiConversationDto>> FindManyAsync(
            Expression<Func<XAiConversation, bool>> predicate,
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiConversation>, IOrderedQueryable<XAiConversation>> orderBuilder = null,
            Func<IQueryable<XAiConversation>, IIncludableQueryable<XAiConversation, object>> includeBuilder = null,
            CancellationToken cancellationToken = default
        )
        {
            return await FindManyAsync(
                fillMessages: false,
                predicate: predicate,
                orderBuilder: orderBuilder,
                includeBuilder: includeBuilder,
                cancellationToken: cancellationToken,
                ignoreSoftDeleteds: ignoreSoftDeleteds
            );
        }

        /// <summary>
        /// retrieve Dtos based on XQuery Pagination structure ...
        /// </summary>
        /// <param name="query"></param>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="predicate"></param>
        /// <param name="orderBuilder"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task<XQueryResult<XAiConversationDto>> QueryAsync(
            XQuery query,
            bool ignoreSoftDeleteds = true,
            Expression<Func<XAiConversation, bool>> predicate = null,
            Func<IQueryable<XAiConversation>, IOrderedQueryable<XAiConversation>> orderBuilder = null,
            Func<IQueryable<XAiConversation>, IIncludableQueryable<XAiConversation, object>> includeBuilder = null,
            CancellationToken cancellationToken = default
        )
        {
            return await QueryAsync(
                query: query,
                fillMessages: false,
                predicate: predicate,
                orderBuilder: orderBuilder,
                includeBuilder: includeBuilder,
                cancellationToken: cancellationToken,
                ignoreSoftDeleteds: ignoreSoftDeleteds
            );
        }
        #endregion
        #endregion

        //
        #region Actions ...
        //
        #region Add ...
        /// <summary>
        /// add a new Dto ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="saveChanges"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<XAiConversationDto> AddAsync(
            XAiConversationDto item,
            bool saveChanges = true,
            bool fillMessages = true,
            CancellationToken cancellationToken = default
        )
        {
            //
            var result = await base.AddAsync(
                item: item,
                saveChanges: saveChanges,
                cancellationToken: cancellationToken
            );

            //
            // Do Enrichment if result Exists ...
            if (!result.IsNullOrDefault())
            {
                //
                await enricher.EnrichAsync(
                    source: result,
                    destination: result,
                    fillMessages: fillMessages,
                    cancellationToken: cancellationToken
                );
            }

            //
            return result;
        }

        /// <summary>
        /// add or update a Dto (add if not exists/update if exists) ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="saveChanges"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<XAiConversationDto> AddOrUpdateAsync(
            XAiConversationDto item,
            bool saveChanges = true,
            bool fillMessages = true,
            CancellationToken cancellationToken = default
        )
        {
            //
            var result = await base.AddAsync(
                item: item,
                saveChanges: saveChanges,
                cancellationToken: cancellationToken
            );

            //
            // Do Enrichment if result Exists ...
            if (!result.IsNullOrDefault())
            {
                //
                await enricher.EnrichAsync(
                    source: result,
                    destination: result,
                    fillMessages: fillMessages,
                    cancellationToken: cancellationToken
                );
            }

            //
            return result;
        }
        #endregion

        //
        #region Update ...
        /// <summary>
        /// Update an Dto values ...
        /// </summary>
        /// <param name="id"></param>
        /// <param name="item"></param>
        /// <param name="saveChanges"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<XAiConversationDto> UpdateAsync(
            Guid id,
            XAiConversationDto item,
            bool saveChanges = true,
            bool fillMessages = true,
            CancellationToken cancellationToken = default
        )
        {
            //
            var result = await base.UpdateAsync(
                id: id,
                item: item,
                saveChanges: saveChanges,
                cancellationToken: cancellationToken
            );

            //
            // Do Enrichment if result Exists ...
            if (!result.IsNullOrDefault())
            {
                //
                await enricher.EnrichAsync(
                    source: result,
                    destination: result,
                    fillMessages: fillMessages,
                    cancellationToken: cancellationToken
                );
            }

            //
            return result;
        }
        #endregion

        //
        #region Remove ...
        /// <summary>
        /// remove an Dto by it's Id ...
        /// </summary>
        /// <param name="id"></param>
        /// <param name="softDelete"></param>
        /// <param name="saveChanges"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<XAiConversationDto> RemoveAsync(
            Guid id,
            bool softDelete = true,
            bool saveChanges = true,
            bool fillMessages = true,
            CancellationToken cancellationToken = default
        )
        {
            //
            var result = await base.RemoveAsync(
                id: id,
                softDelete: softDelete,
                saveChanges: saveChanges,
                cancellationToken: cancellationToken
            );

            //
            // Do Enrichment if result Exists ...
            if (!result.IsNullOrDefault())
            {
                //
                await enricher.EnrichAsync(
                    source: result,
                    destination: result,
                    fillMessages: fillMessages,
                    cancellationToken: cancellationToken
                );
            }

            //
            return result;
        }

        /// <summary>
        /// remove an Dto ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="softDelete"></param>
        /// <param name="saveChanges"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<XAiConversationDto> RemoveAsync(
            XAiConversationDto item,
            bool softDelete = true,
            bool saveChanges = true,
            bool fillMessages = true,
            CancellationToken cancellationToken = default
        )
        {
            //
            var result = await base.RemoveAsync(
                item: item,
                softDelete: softDelete,
                saveChanges: saveChanges,
                cancellationToken: cancellationToken
            );

            //
            // Do Enrichment if result Exists ...
            if (!result.IsNullOrDefault())
            {
                //
                await enricher.EnrichAsync(
                    source: result,
                    destination: result,
                    fillMessages: fillMessages,
                    cancellationToken: cancellationToken
                );
            }

            //
            return result;
        }
        #endregion

        //
        #region Retrieve ...
        /// <summary>
        /// retrieve an Dto by it's Id ...
        /// </summary>
        /// <param name="id"></param>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<XAiConversationDto> GetAsync(
            Guid id,
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiConversation>, IIncludableQueryable<XAiConversation, object>> includeBuilder = null,
            bool fillMessages = true,
            CancellationToken cancellationToken = default
        )
        {
            //
            var result = await base.GetAsync(
                id: id,
                includeBuilder: includeBuilder,
                cancellationToken: cancellationToken,
                ignoreSoftDeleteds: ignoreSoftDeleteds
            );

            //
            // Do Enrichment if result Exists ...
            if (!result.IsNullOrDefault())
            {
                //
                await enricher.EnrichAsync(
                    source: result,
                    destination: result,
                    fillMessages: fillMessages,
                    cancellationToken: cancellationToken
                );
            }

            //
            return result;
        }

        /// <summary>
        /// retrieve all exists Dtos ...
        /// </summary>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="orderBuilder"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<IEnumerable<XAiConversationDto>> GetAllAsync(
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiConversation>, IOrderedQueryable<XAiConversation>> orderBuilder = null,
            Func<IQueryable<XAiConversation>, IIncludableQueryable<XAiConversation, object>> includeBuilder = null,
            bool fillMessages = true,
            CancellationToken cancellationToken = default
        )
        {
            //
            var result = await base.GetAllAsync(
                orderBuilder: orderBuilder,
                includeBuilder: includeBuilder,
                cancellationToken: cancellationToken,
                ignoreSoftDeleteds: ignoreSoftDeleteds
            );

            //
            // Do Enrichment if result Exists ...
            if (!result.IsNullOrDefault())
            {
                //
                await enricher.EnrichRangeAsync(
                    sources: result,
                    destinations: result,
                    fillMessages: fillMessages,
                    cancellationToken: cancellationToken
                );
            }

            //
            return result;
        }

        /// <summary>
        /// retrieve all exists Dtos
        /// as Async Enumerable ...
        /// </summary>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="orderBuilder"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async IAsyncEnumerable<XAiConversationDto> GetAllAsAsyncEnumerable(
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiConversation>, IOrderedQueryable<XAiConversation>> orderBuilder = null,
            Func<IQueryable<XAiConversation>, IIncludableQueryable<XAiConversation, object>> includeBuilder = null,
            bool fillMessages = true,
            [EnumeratorCancellation] CancellationToken cancellationToken = default
        )
        {
            //
            var enumerable = base.GetAllAsAsyncEnumerable(
                orderBuilder: orderBuilder,
                includeBuilder: includeBuilder,
                ignoreSoftDeleteds: ignoreSoftDeleteds
            );
            await foreach (var dto in enumerable)
            {
                //
                if (cancellationToken.IsCancellationRequested)
                {
                    yield break;
                }

                //
                await enricher.EnrichAsync(
                    source: dto,
                    destination: dto,
                    fillMessages: fillMessages,
                    cancellationToken: cancellationToken
                );
                yield return dto;
            }
        }

        /// <summary>
        /// find an Dto by providing a Conditional Expression ...
        /// </summary>
        /// <param name="predicate"></param>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<XAiConversationDto> FindOneAsync(
            Expression<Func<XAiConversation, bool>> predicate,
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiConversation>, IIncludableQueryable<XAiConversation, object>> includeBuilder = null,
            bool fillMessages = true,
            CancellationToken cancellationToken = default
        )
        {
            //
            var result = await base.FindOneAsync(
                predicate: predicate,
                includeBuilder: includeBuilder,
                cancellationToken: cancellationToken,
                ignoreSoftDeleteds: ignoreSoftDeleteds
            );

            //
            // Do Enrichment if result Exists ...
            if (!result.IsNullOrDefault())
            {
                //
                await enricher.EnrichAsync(
                    source: result,
                    destination: result,
                    fillMessages: fillMessages,
                    cancellationToken: cancellationToken
                );
            }

            //
            return result;
        }

        /// <summary>
        /// find a collection of Dtos by proving a Conditional Expression ...
        /// </summary>
        /// <param name="predicate"></param>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="orderBuilder"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<IEnumerable<XAiConversationDto>> FindManyAsync(
            Expression<Func<XAiConversation, bool>> predicate,
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiConversation>, IOrderedQueryable<XAiConversation>> orderBuilder = null,
            Func<IQueryable<XAiConversation>, IIncludableQueryable<XAiConversation, object>> includeBuilder = null,
            bool fillMessages = true,
            CancellationToken cancellationToken = default
        )
        {
            //
            var result = await base.FindManyAsync(
                predicate: predicate,
                orderBuilder: orderBuilder,
                includeBuilder: includeBuilder,
                cancellationToken: cancellationToken,
                ignoreSoftDeleteds: ignoreSoftDeleteds
            );

            //
            // Do Enrichment if result Exists ...
            if (!result.IsNullOrDefault())
            {
                //
                await enricher.EnrichRangeAsync(
                    sources: result,
                    destinations: result,
                    fillMessages: fillMessages,
                    cancellationToken: cancellationToken
                );
            }

            //
            return result;
        }

        /// <summary>
        /// retrieve Dtos based on XQuery Pagination structure ...
        /// </summary>
        /// <param name="query"></param>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="predicate"></param>
        /// <param name="orderBuilder"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<XQueryResult<XAiConversationDto>> QueryAsync(
            XQuery query,
            bool ignoreSoftDeleteds = true,
            Expression<Func<XAiConversation, bool>> predicate = null,
            Func<IQueryable<XAiConversation>, IOrderedQueryable<XAiConversation>> orderBuilder = null,
            Func<IQueryable<XAiConversation>, IIncludableQueryable<XAiConversation, object>> includeBuilder = null,
            bool fillMessages = true,
            CancellationToken cancellationToken = default
        )
        {
            //
            var result = await base.QueryAsync(
                query: query,
                predicate: predicate,
                orderBuilder: orderBuilder,
                includeBuilder: includeBuilder,
                cancellationToken: cancellationToken,
                ignoreSoftDeleteds: ignoreSoftDeleteds
            );

            //
            // Do Enrichment if result Exists ...
            if (!result.IsNullOrDefault())
            {
                //
                await enricher.EnrichRangeAsync(
                    sources: result.Items,
                    destinations: result.Items,
                    fillMessages: fillMessages,
                    cancellationToken: cancellationToken
                );
            }

            //
            return result;
        }
        #endregion
        #endregion
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Dtos\XAiConversationServiceProvider.cs

using System;
using Microsoft.AspNetCore.SignalR;
using xAiModels.Interfaces.Dtos;
using xAiModels.Models.Dtos;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xIdentityService.Interfaces;
using xPushService.Base;

namespace xAiModels.Providers.Dtos
{
    public class XAiConversationServiceProvider : XBaseDtoProvider<XAiConversation, XAiConversationDto, Guid, XAiConversationDtoHub>, IXAiConversationServiceProvider
    {
        public XAiConversationServiceProvider(
            IHubContext<XAiConversationDtoHub> hub,
            XDataServiceConfiguration dataConfiguration,
            IXAiConversationRepositoryService service,
            IXIdentityProvider identityProvider = null
        ) : base(
            hub,
            dataConfiguration,
            service,
            identityProvider
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Dtos\XAiMessageDtoHub.cs

using System;
using Microsoft.Extensions.Logging;
using xAiModels.Interfaces.Dtos;
using xAiModels.Models.Dtos;
using xAiModels.Models.Entities;
using xPushService.Base;

namespace xAiModels.Providers.Dtos
{
    public class XAiMessageDtoHub : XBaseDtoHub<XAiMessage, XAiMessageDto, Guid>, IXAiMessageDtoHub
    {
        public XAiMessageDtoHub(
            ILogger<XAiMessageDtoHub> logger
        ) : base(logger)
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Dtos\XAiMessageRepositoryService.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Query;
using xAiModels.Configurations;
using xAiModels.Interfaces.Dtos;
using xAiModels.Interfaces.Enrichers;
using xAiModels.Interfaces.Entities;
using xAiModels.Models.Dtos;
using xAiModels.Models.Entities;
using xCommons.Extensions;
using xDataService.Providers;
using xModels.Dtos;

namespace xAiModels.Providers.Dtos
{
    public class XAiMessageRepositoryService : XBaseRepositoryService<XAiMessage, XAiMessageDto, Guid>, IXAiMessageRepositoryService
    {
        private readonly IXAiMessageEnricher enricher;

        public XAiMessageRepositoryService(
            IXAiMessageEnricher enricher,
            IXAiMessageRepository repository
        ) : base(
            repository,
            mapperProfiles:
            [
                new XAiModelMappersProfile()
            ]
        )
        {
            this.enricher = enricher;
        }

        //
        #region Actions ...
        //
        #region Add ...
        /// <summary>
        /// add a new Dto ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="saveChanges"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task<XAiMessageDto> AddAsync(
            XAiMessageDto item,
            bool saveChanges = true,
            CancellationToken cancellationToken = default
        )
        {
            //
            var result = await base.AddAsync(
                item: item,
                saveChanges: saveChanges,
                cancellationToken: cancellationToken
            );

            //
            // Do Enrichment if result Exists ...
            if (!result.IsNullOrDefault())
            {
                //
                await enricher.EnrichAsync(
                    source: result,
                    destination: result,
                    cancellationToken: cancellationToken
                );
            }

            //
            return result;
        }

        /// <summary>
        /// add or update a Dto (add if not exists/update if exists) ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="saveChanges"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task<XAiMessageDto> AddOrUpdateAsync(
            XAiMessageDto item,
            bool saveChanges = true,
            CancellationToken cancellationToken = default
        )
        {
            //
            var result = await base.AddAsync(
                item: item,
                saveChanges: saveChanges,
                cancellationToken: cancellationToken
            );

            //
            // Do Enrichment if result Exists ...
            if (!result.IsNullOrDefault())
            {
                //
                await enricher.EnrichAsync(
                    source: result,
                    destination: result,
                    cancellationToken: cancellationToken
                );
            }

            //
            return result;
        }
        #endregion

        //
        #region Update ...
        /// <summary>
        /// Update an Dto values ...
        /// </summary>
        /// <param name="id"></param>
        /// <param name="item"></param>
        /// <param name="saveChanges"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task<XAiMessageDto> UpdateAsync(
            Guid id,
            XAiMessageDto item,
            bool saveChanges = true,
            CancellationToken cancellationToken = default
        )
        {
            //
            var result = await base.UpdateAsync(
                id: id,
                item: item,
                saveChanges: saveChanges,
                cancellationToken: cancellationToken
            );

            //
            // Do Enrichment if result Exists ...
            if (!result.IsNullOrDefault())
            {
                //
                await enricher.EnrichAsync(
                    source: result,
                    destination: result,
                    cancellationToken: cancellationToken
                );
            }

            //
            return result;
        }
        #endregion

        //
        #region Remove ...
        /// <summary>
        /// remove an Dto by it's Id ...
        /// </summary>
        /// <param name="id"></param>
        /// <param name="softDelete"></param>
        /// <param name="saveChanges"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task<XAiMessageDto> RemoveAsync(
            Guid id,
            bool softDelete = true,
            bool saveChanges = true,
            CancellationToken cancellationToken = default
        )
        {
            //
            var result = await base.RemoveAsync(
                id: id,
                softDelete: softDelete,
                saveChanges: saveChanges,
                cancellationToken: cancellationToken
            );

            //
            // Do Enrichment if result Exists ...
            if (!result.IsNullOrDefault())
            {
                //
                await enricher.EnrichAsync(
                    source: result,
                    destination: result,
                    cancellationToken: cancellationToken
                );
            }

            //
            return result;
        }

        /// <summary>
        /// remove an Dto ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="softDelete"></param>
        /// <param name="saveChanges"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task<XAiMessageDto> RemoveAsync(
            XAiMessageDto item,
            bool softDelete = true,
            bool saveChanges = true,
            CancellationToken cancellationToken = default
        )
        {
            //
            var result = await base.RemoveAsync(
                item: item,
                softDelete: softDelete,
                saveChanges: saveChanges,
                cancellationToken: cancellationToken
            );

            //
            // Do Enrichment if result Exists ...
            if (!result.IsNullOrDefault())
            {
                //
                await enricher.EnrichAsync(
                    source: result,
                    destination: result,
                    cancellationToken: cancellationToken
                );
            }

            //
            return result;
        }
        #endregion

        //
        #region Retrieve ...
        /// <summary>
        /// retrieve an Dto by it's Id ...
        /// </summary>
        /// <param name="id"></param>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task<XAiMessageDto> GetAsync(
            Guid id,
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiMessage>, IIncludableQueryable<XAiMessage, object>> includeBuilder = null,
            CancellationToken cancellationToken = default
        )
        {
            //
            var result = await base.GetAsync(
                id: id,
                includeBuilder: includeBuilder,
                cancellationToken: cancellationToken,
                ignoreSoftDeleteds: ignoreSoftDeleteds
            );

            //
            // Do Enrichment if result Exists ...
            if (!result.IsNullOrDefault())
            {
                //
                await enricher.EnrichAsync(
                    source: result,
                    destination: result,
                    cancellationToken: cancellationToken
                );
            }

            //
            return result;
        }

        /// <summary>
        /// retrieve all exists Dtos ...
        /// </summary>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="orderBuilder"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task<IEnumerable<XAiMessageDto>> GetAllAsync(
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiMessage>, IOrderedQueryable<XAiMessage>> orderBuilder = null,
            Func<IQueryable<XAiMessage>, IIncludableQueryable<XAiMessage, object>> includeBuilder = null,
            CancellationToken cancellationToken = default
        )
        {
            //
            var result = await base.GetAllAsync(
                orderBuilder: orderBuilder,
                includeBuilder: includeBuilder,
                cancellationToken: cancellationToken,
                ignoreSoftDeleteds: ignoreSoftDeleteds
            );

            //
            // Do Enrichment if result Exists ...
            if (!result.IsNullOrDefault())
            {
                //
                await enricher.EnrichRangeAsync(
                    sources: result,
                    destinations: result,
                    cancellationToken: cancellationToken
                );
            }

            //
            return result;
        }

        /// <summary>
        /// retrieve all exists Dtos
        /// as Async Enumerable ...
        /// </summary>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="orderBuilder"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async IAsyncEnumerable<XAiMessageDto> GetAllAsAsyncEnumerable(
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiMessage>, IOrderedQueryable<XAiMessage>> orderBuilder = null,
            Func<IQueryable<XAiMessage>, IIncludableQueryable<XAiMessage, object>> includeBuilder = null,
            [EnumeratorCancellation] CancellationToken cancellationToken = default
        )
        {
            //
            var enumerable = base.GetAllAsAsyncEnumerable(
                orderBuilder: orderBuilder,
                includeBuilder: includeBuilder,
                ignoreSoftDeleteds: ignoreSoftDeleteds
            );
            await foreach (var dto in enumerable)
            {
                //
                if (cancellationToken.IsCancellationRequested)
                {
                    yield break;
                }

                //
                await enricher.EnrichAsync(
                    source: dto,
                    destination: dto,
                    cancellationToken: cancellationToken
                );
                yield return dto;
            }
        }

        /// <summary>
        /// find an Dto by providing a Conditional Expression ...
        /// </summary>
        /// <param name="predicate"></param>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task<XAiMessageDto> FindOneAsync(
            Expression<Func<XAiMessage, bool>> predicate,
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiMessage>, IIncludableQueryable<XAiMessage, object>> includeBuilder = null,
            CancellationToken cancellationToken = default
        )
        {
            //
            var result = await base.FindOneAsync(
                predicate: predicate,
                includeBuilder: includeBuilder,
                cancellationToken: cancellationToken,
                ignoreSoftDeleteds: ignoreSoftDeleteds
            );

            //
            // Do Enrichment if result Exists ...
            if (!result.IsNullOrDefault())
            {
                //
                await enricher.EnrichAsync(
                    source: result,
                    destination: result,
                    cancellationToken: cancellationToken
                );
            }

            //
            return result;
        }

        /// <summary>
        /// find a collection of Dtos by proving a Conditional Expression ...
        /// </summary>
        /// <param name="predicate"></param>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="orderBuilder"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task<IEnumerable<XAiMessageDto>> FindManyAsync(
            Expression<Func<XAiMessage, bool>> predicate,
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiMessage>, IOrderedQueryable<XAiMessage>> orderBuilder = null,
            Func<IQueryable<XAiMessage>, IIncludableQueryable<XAiMessage, object>> includeBuilder = null,
            CancellationToken cancellationToken = default
        )
        {
            //
            var result = await base.FindManyAsync(
                predicate: predicate,
                orderBuilder: orderBuilder,
                includeBuilder: includeBuilder,
                cancellationToken: cancellationToken,
                ignoreSoftDeleteds: ignoreSoftDeleteds
            );

            //
            // Do Enrichment if result Exists ...
            if (!result.IsNullOrDefault())
            {
                //
                await enricher.EnrichRangeAsync(
                    sources: result,
                    destinations: result,
                    cancellationToken: cancellationToken
                );
            }

            //
            return result;
        }

        /// <summary>
        /// retrieve Dtos based on XQuery Pagination structure ...
        /// </summary>
        /// <param name="query"></param>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="predicate"></param>
        /// <param name="orderBuilder"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task<XQueryResult<XAiMessageDto>> QueryAsync(
            XQuery query,
            bool ignoreSoftDeleteds = true,
            Expression<Func<XAiMessage, bool>> predicate = null,
            Func<IQueryable<XAiMessage>, IOrderedQueryable<XAiMessage>> orderBuilder = null,
            Func<IQueryable<XAiMessage>, IIncludableQueryable<XAiMessage, object>> includeBuilder = null,
            CancellationToken cancellationToken = default
        )
        {
            //
            var result = await base.QueryAsync(
                query: query,
                predicate: predicate,
                orderBuilder: orderBuilder,
                includeBuilder: includeBuilder,
                cancellationToken: cancellationToken,
                ignoreSoftDeleteds: ignoreSoftDeleteds
            );

            //
            // Do Enrichment if result Exists ...
            if (!result.IsNullOrDefault())
            {
                //
                await enricher.EnrichRangeAsync(
                    sources: result.Items,
                    destinations: result.Items,
                    cancellationToken: cancellationToken
                );
            }

            //
            return result;
        }
        #endregion
        #endregion
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Dtos\XAiMessageServiceProvider.cs

using System;
using Microsoft.AspNetCore.SignalR;
using xAiModels.Interfaces.Dtos;
using xAiModels.Models.Dtos;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xIdentityService.Interfaces;
using xPushService.Base;

namespace xAiModels.Providers.Dtos
{
    public class XAiMessageServiceProvider : XBaseDtoProvider<XAiMessage, XAiMessageDto, Guid, XAiMessageDtoHub>, IXAiMessageServiceProvider
    {
        public XAiMessageServiceProvider(
            IHubContext<XAiMessageDtoHub> hub,
            XDataServiceConfiguration dataConfiguration,
            IXAiMessageRepositoryService service,
            IXIdentityProvider identityProvider = null
        ) : base(
            hub,
            dataConfiguration,
            service,
            identityProvider
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Dtos\XAiProjectDtoHub.cs

using System;
using Microsoft.Extensions.Logging;
using xAiModels.Interfaces.Dtos;
using xAiModels.Models.Dtos;
using xAiModels.Models.Entities;
using xPushService.Base;

namespace xAiModels.Providers.Dtos
{
    public class XAiProjectDtoHub : XBaseDtoHub<XAiProject, XAiProjectDto, Guid>, IXAiProjectDtoHub
    {
        public XAiProjectDtoHub(
            ILogger<XAiProjectDtoHub> logger
        ) : base(logger)
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Dtos\XAiProjectRepositoryService.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Query;
using xAiModels.Configurations;
using xAiModels.Interfaces.Dtos;
using xAiModels.Interfaces.Enrichers;
using xAiModels.Interfaces.Entities;
using xAiModels.Models.Dtos;
using xAiModels.Models.Entities;
using xCommons.Extensions;
using xDataService.Providers;
using xModels.Dtos;

namespace xAiModels.Providers.Dtos
{
    public class XAiProjectRepositoryService : XBaseRepositoryService<XAiProject, XAiProjectDto, Guid>, IXAiProjectRepositoryService
    {
        public XAiProjectRepositoryService(
            IXAiProjectRepository repository
        ) : base(
            repository,
            mapperProfiles:
            [
                new XAiModelMappersProfile()
            ]
        )
        { }


        //
        #region Actions Base ...
        //
        #region Add ...
        /// <summary>
        /// add a new Dto ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="saveChanges"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task<XAiProjectDto> AddAsync(
            XAiProjectDto item,
            bool saveChanges = true,
            CancellationToken cancellationToken = default
        )
        {
            return await AddAsync(
                item: item,
                saveChanges: saveChanges,
                fillConversations: false,
                fillConversationMessages: false,
                cancellationToken: cancellationToken
            );
        }

        /// <summary>
        /// add or update a Dto (add if not exists/update if exists) ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="saveChanges"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task<XAiProjectDto> AddOrUpdateAsync(
            XAiProjectDto item,
            bool saveChanges = true,
            CancellationToken cancellationToken = default
        )
        {
            return await AddOrUpdateAsync(
                item: item,
                saveChanges: saveChanges,
                fillConversations: false,
                fillConversationMessages: false,
                cancellationToken: cancellationToken
            );
        }
        #endregion

        //
        #region Update ...
        /// <summary>
        /// Update an Dto values ...
        /// </summary>
        /// <param name="id"></param>
        /// <param name="item"></param>
        /// <param name="saveChanges"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task<XAiProjectDto> UpdateAsync(
            Guid id,
            XAiProjectDto item,
            bool saveChanges = true,
            CancellationToken cancellationToken = default
        )
        {
            return await UpdateAsync(
                id: id,
                item: item,
                saveChanges: saveChanges,
                fillConversations: false,
                fillConversationMessages: false,
                cancellationToken: cancellationToken
            );
        }
        #endregion

        //
        #region Remove ...
        /// <summary>
        /// remove an Dto by it's Id ...
        /// </summary>
        /// <param name="id"></param>
        /// <param name="softDelete"></param>
        /// <param name="saveChanges"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task<XAiProjectDto> RemoveAsync(
            Guid id,
            bool softDelete = false,
            bool saveChanges = true,
            CancellationToken cancellationToken = default
        )
        {
            return await RemoveAsync(
                id: id,
                softDelete: softDelete,
                saveChanges: saveChanges,
                fillConversations: false,
                fillConversationMessages: false,
                cancellationToken: cancellationToken
            );
        }

        /// <summary>
        /// remove an Dto ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="softDelete"></param>
        /// <param name="saveChanges"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task<XAiProjectDto> RemoveAsync(
            XAiProjectDto item,
            bool softDelete = false,
            bool saveChanges = true,
            CancellationToken cancellationToken = default
        )
        {
            return await RemoveAsync(
                item: item,
                softDelete: softDelete,
                saveChanges: saveChanges,
                fillConversations: false,
                fillConversationMessages: false,
                cancellationToken: cancellationToken
            );
        }
        #endregion

        //
        #region Retrieve ...
        /// <summary>
        /// retrieve an Dto by it's Id ...
        /// </summary>
        /// <param name="id"></param>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task<XAiProjectDto> GetAsync(
            Guid id,
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiProject>, IIncludableQueryable<XAiProject, object>> includeBuilder = null,
            CancellationToken cancellationToken = default
        )
        {
            return await GetAsync(
                id: id,
                fillConversations: false,
                includeBuilder: includeBuilder,
                fillConversationMessages: false,
                cancellationToken: cancellationToken,
                ignoreSoftDeleteds: ignoreSoftDeleteds
            );
        }

        /// <summary>
        /// retrieve all exists Dtos ...
        /// </summary>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="orderBuilder"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task<IEnumerable<XAiProjectDto>> GetAllAsync(
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiProject>, IOrderedQueryable<XAiProject>> orderBuilder = null,
            Func<IQueryable<XAiProject>, IIncludableQueryable<XAiProject, object>> includeBuilder = null,
            CancellationToken cancellationToken = default
        )
        {
            return await GetAllAsync(
                fillConversations: false,
                orderBuilder: orderBuilder,
                includeBuilder: includeBuilder,
                fillConversationMessages: false,
                cancellationToken: cancellationToken,
                ignoreSoftDeleteds: ignoreSoftDeleteds
            );
        }

        /// <summary>
        /// retrieve all exists Dtos
        /// as Async Enumerable ...
        /// </summary>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="orderBuilder"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override IAsyncEnumerable<XAiProjectDto> GetAllAsAsyncEnumerable(
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiProject>, IOrderedQueryable<XAiProject>> orderBuilder = null,
            Func<IQueryable<XAiProject>, IIncludableQueryable<XAiProject, object>> includeBuilder = null,
            CancellationToken cancellationToken = default
        )
        {
            return GetAllAsAsyncEnumerable(
                fillConversations: false,
                orderBuilder: orderBuilder,
                includeBuilder: includeBuilder,
                fillConversationMessages: false,
                cancellationToken: cancellationToken,
                ignoreSoftDeleteds: ignoreSoftDeleteds
            );
        }

        /// <summary>
        /// find an Dto by providing a Conditional Expression ...
        /// </summary>
        /// <param name="predicate"></param>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task<XAiProjectDto> FindOneAsync(
            Expression<Func<XAiProject, bool>> predicate,
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiProject>, IIncludableQueryable<XAiProject, object>> includeBuilder = null,
            CancellationToken cancellationToken = default
        )
        {
            return await FindOneAsync(
                predicate: predicate,
                fillConversations: false,
                includeBuilder: includeBuilder,
                fillConversationMessages: false,
                cancellationToken: cancellationToken,
                ignoreSoftDeleteds: ignoreSoftDeleteds
            );
        }

        /// <summary>
        /// find a collection of Dtos by proving a Conditional Expression ...
        /// </summary>
        /// <param name="predicate"></param>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="orderBuilder"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task<IEnumerable<XAiProjectDto>> FindManyAsync(
            Expression<Func<XAiProject, bool>> predicate,
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiProject>, IOrderedQueryable<XAiProject>> orderBuilder = null,
            Func<IQueryable<XAiProject>, IIncludableQueryable<XAiProject, object>> includeBuilder = null,
            CancellationToken cancellationToken = default
        )
        {
            return await FindManyAsync(
                predicate: predicate,
                fillConversations: false,
                orderBuilder: orderBuilder,
                includeBuilder: includeBuilder,
                fillConversationMessages: false,
                cancellationToken: cancellationToken,
                ignoreSoftDeleteds: ignoreSoftDeleteds
            );
        }

        /// <summary>
        /// retrieve Dtos based on XQuery Pagination structure ...
        /// </summary>
        /// <param name="query"></param>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="predicate"></param>
        /// <param name="orderBuilder"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public override async Task<XQueryResult<XAiProjectDto>> QueryAsync(
            XQuery query,
            bool ignoreSoftDeleteds = true,
            Expression<Func<XAiProject, bool>> predicate = null,
            Func<IQueryable<XAiProject>, IOrderedQueryable<XAiProject>> orderBuilder = null,
            Func<IQueryable<XAiProject>, IIncludableQueryable<XAiProject, object>> includeBuilder = null,
            CancellationToken cancellationToken = default
        )
        {
            return await QueryAsync(
                query: query,
                predicate: predicate,
                fillConversations: false,
                orderBuilder: orderBuilder,
                includeBuilder: includeBuilder,
                fillConversationMessages: false,
                cancellationToken: cancellationToken,
                ignoreSoftDeleteds: ignoreSoftDeleteds
            );
        }
        #endregion
        #endregion

        //
        #region Actions ...
        //
        #region Add ...
        /// <summary>
        /// add a new Dto ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="saveChanges"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<XAiProjectDto> AddAsync(
            XAiProjectDto item,
            bool saveChanges = true,
            bool fillConversations = true,
            bool fillConversationMessages = true,
            CancellationToken cancellationToken = default
        )
        {
            //
            var result = await base.AddAsync(
                item: item,
                saveChanges: saveChanges,
                cancellationToken: cancellationToken
            );

            //
            // Do Enrichment if result Exists ...
            if (!result.IsNullOrDefault())
            {
                //
                await enricher.EnrichAsync(
                    source: result,
                    destination: result,
                    fillConversations: fillConversations,
                    cancellationToken: cancellationToken,
                    fillConversationMessages: fillConversationMessages
                );
            }

            //
            return result;
        }

        /// <summary>
        /// add or update a Dto (add if not exists/update if exists) ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="saveChanges"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<XAiProjectDto> AddOrUpdateAsync(
            XAiProjectDto item,
            bool saveChanges = true,
            bool fillConversations = true,
            bool fillConversationMessages = true,
            CancellationToken cancellationToken = default
        )
        {
            //
            var result = await base.AddAsync(
                item: item,
                saveChanges: saveChanges,
                cancellationToken: cancellationToken
            );

            //
            // Do Enrichment if result Exists ...
            if (!result.IsNullOrDefault())
            {
                //
                await enricher.EnrichAsync(
                    source: result,
                    destination: result,
                    fillConversations: fillConversations,
                    cancellationToken: cancellationToken,
                    fillConversationMessages: fillConversationMessages
                );
            }

            //
            return result;
        }
        #endregion

        //
        #region Update ...
        /// <summary>
        /// Update an Dto values ...
        /// </summary>
        /// <param name="id"></param>
        /// <param name="item"></param>
        /// <param name="saveChanges"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<XAiProjectDto> UpdateAsync(
            Guid id,
            XAiProjectDto item,
            bool saveChanges = true,
            bool fillConversations = true,
            bool fillConversationMessages = true,
            CancellationToken cancellationToken = default
        )
        {
            //
            var result = await base.UpdateAsync(
                id: id,
                item: item,
                saveChanges: saveChanges,
                cancellationToken: cancellationToken
            );

            //
            // Do Enrichment if result Exists ...
            if (!result.IsNullOrDefault())
            {
                //
                await enricher.EnrichAsync(
                    source: result,
                    destination: result,
                    fillConversations: fillConversations,
                    cancellationToken: cancellationToken,
                    fillConversationMessages: fillConversationMessages
                );
            }

            //
            return result;
        }
        #endregion

        //
        #region Remove ...
        /// <summary>
        /// remove an Dto by it's Id ...
        /// </summary>
        /// <param name="id"></param>
        /// <param name="softDelete"></param>
        /// <param name="saveChanges"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<XAiProjectDto> RemoveAsync(
            Guid id,
            bool softDelete = false,
            bool saveChanges = true,
            bool fillConversations = true,
            bool fillConversationMessages = true,
            CancellationToken cancellationToken = default
        )
        {
            //
            var result = await base.RemoveAsync(
                id: id,
                softDelete: softDelete,
                saveChanges: saveChanges,
                cancellationToken: cancellationToken
            );

            //
            // Do Enrichment if result Exists ...
            if (!result.IsNullOrDefault())
            {
                //
                await enricher.EnrichAsync(
                    source: result,
                    destination: result,
                    fillConversations: fillConversations,
                    cancellationToken: cancellationToken,
                    fillConversationMessages: fillConversationMessages
                );
            }

            //
            return result;
        }

        /// <summary>
        /// remove an Dto ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="softDelete"></param>
        /// <param name="saveChanges"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<XAiProjectDto> RemoveAsync(
            XAiProjectDto item,
            bool softDelete = false,
            bool saveChanges = true,
            bool fillConversations = true,
            bool fillConversationMessages = true,
            CancellationToken cancellationToken = default
        )
        {
            //
            var result = await base.RemoveAsync(
                item: item,
                softDelete: softDelete,
                saveChanges: saveChanges,
                cancellationToken: cancellationToken
            );

            //
            // Do Enrichment if result Exists ...
            if (!result.IsNullOrDefault())
            {
                //
                await enricher.EnrichAsync(
                    source: result,
                    destination: result,
                    fillConversations: fillConversations,
                    cancellationToken: cancellationToken,
                    fillConversationMessages: fillConversationMessages
                );
            }

            //
            return result;
        }
        #endregion

        //
        #region Retrieve ...
        /// <summary>
        /// retrieve an Dto by it's Id ...
        /// </summary>
        /// <param name="id"></param>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<XAiProjectDto> GetAsync(
            Guid id,
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiProject>, IIncludableQueryable<XAiProject, object>> includeBuilder = null,
            bool fillConversations = true,
            bool fillConversationMessages = true,
            CancellationToken cancellationToken = default
        )
        {
            //
            var result = await base.GetAsync(
                id: id,
                includeBuilder: includeBuilder,
                cancellationToken: cancellationToken,
                ignoreSoftDeleteds: ignoreSoftDeleteds
            );

            //
            // Do Enrichment if result Exists ...
            if (!result.IsNullOrDefault())
            {
                //
                await enricher.EnrichAsync(
                    source: result,
                    destination: result,
                    fillConversations: fillConversations,
                    cancellationToken: cancellationToken,
                    fillConversationMessages: fillConversationMessages
                );
            }

            //
            return result;
        }

        /// <summary>
        /// retrieve all exists Dtos ...
        /// </summary>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="orderBuilder"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<IEnumerable<XAiProjectDto>> GetAllAsync(
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiProject>, IOrderedQueryable<XAiProject>> orderBuilder = null,
            Func<IQueryable<XAiProject>, IIncludableQueryable<XAiProject, object>> includeBuilder = null,
            bool fillConversations = true,
            bool fillConversationMessages = true,
            CancellationToken cancellationToken = default
        )
        {
            //
            var result = await base.GetAllAsync(
                orderBuilder: orderBuilder,
                includeBuilder: includeBuilder,
                cancellationToken: cancellationToken,
                ignoreSoftDeleteds: ignoreSoftDeleteds
            );

            //
            // Do Enrichment if result Exists ...
            if (!result.IsNullOrDefault())
            {
                //
                await enricher.EnrichRangeAsync(
                    sources: result,
                    destinations: result,
                    fillConversations: fillConversations,
                    cancellationToken: cancellationToken,
                    fillConversationMessages: fillConversationMessages
                );
            }

            //
            return result;
        }

        /// <summary>
        /// retrieve all exists Dtos
        /// as Async Enumerable ...
        /// </summary>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="orderBuilder"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async IAsyncEnumerable<XAiProjectDto> GetAllAsAsyncEnumerable(
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiProject>, IOrderedQueryable<XAiProject>> orderBuilder = null,
            Func<IQueryable<XAiProject>, IIncludableQueryable<XAiProject, object>> includeBuilder = null,
            bool fillConversations = true,
            bool fillConversationMessages = true,
            [EnumeratorCancellation] CancellationToken cancellationToken = default
        )
        {
            //
            var enumerable = base.GetAllAsAsyncEnumerable(
                orderBuilder: orderBuilder,
                includeBuilder: includeBuilder,
                ignoreSoftDeleteds: ignoreSoftDeleteds
            );
            await foreach (var dto in enumerable)
            {
                //
                if (cancellationToken.IsCancellationRequested)
                {
                    yield break;
                }

                //
                await enricher.EnrichAsync(
                    source: dto,
                    destination: dto,
                    fillConversations: fillConversations,
                    cancellationToken: cancellationToken,
                    fillConversationMessages: fillConversationMessages
                );
                yield return dto;
            }
        }

        /// <summary>
        /// find an Dto by providing a Conditional Expression ...
        /// </summary>
        /// <param name="predicate"></param>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<XAiProjectDto> FindOneAsync(
            Expression<Func<XAiProject, bool>> predicate,
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiProject>, IIncludableQueryable<XAiProject, object>> includeBuilder = null,
            bool fillConversations = true,
            bool fillConversationMessages = true,
            CancellationToken cancellationToken = default
        )
        {
            //
            var result = await base.FindOneAsync(
                predicate: predicate,
                includeBuilder: includeBuilder,
                cancellationToken: cancellationToken,
                ignoreSoftDeleteds: ignoreSoftDeleteds
            );

            //
            // Do Enrichment if result Exists ...
            if (!result.IsNullOrDefault())
            {
                //
                await enricher.EnrichAsync(
                    source: result,
                    destination: result,
                    fillConversations: fillConversations,
                    cancellationToken: cancellationToken,
                    fillConversationMessages: fillConversationMessages
                );
            }

            //
            return result;
        }

        /// <summary>
        /// find a collection of Dtos by proving a Conditional Expression ...
        /// </summary>
        /// <param name="predicate"></param>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="orderBuilder"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<IEnumerable<XAiProjectDto>> FindManyAsync(
            Expression<Func<XAiProject, bool>> predicate,
            bool ignoreSoftDeleteds = true,
            Func<IQueryable<XAiProject>, IOrderedQueryable<XAiProject>> orderBuilder = null,
            Func<IQueryable<XAiProject>, IIncludableQueryable<XAiProject, object>> includeBuilder = null,
            bool fillConversations = true,
            bool fillConversationMessages = true,
            CancellationToken cancellationToken = default
        )
        {
            //
            var result = await base.FindManyAsync(
                predicate: predicate,
                orderBuilder: orderBuilder,
                includeBuilder: includeBuilder,
                cancellationToken: cancellationToken,
                ignoreSoftDeleteds: ignoreSoftDeleteds
            );

            //
            // Do Enrichment if result Exists ...
            if (!result.IsNullOrDefault())
            {
                //
                await enricher.EnrichRangeAsync(
                    sources: result,
                    destinations: result,
                    fillConversations: fillConversations,
                    cancellationToken: cancellationToken,
                    fillConversationMessages: fillConversationMessages
                );
            }

            //
            return result;
        }

        /// <summary>
        /// retrieve Dtos based on XQuery Pagination structure ...
        /// </summary>
        /// <param name="query"></param>
        /// <param name="ignoreSoftDeleteds"></param>
        /// <param name="predicate"></param>
        /// <param name="orderBuilder"></param>
        /// <param name="includeBuilder"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<XQueryResult<XAiProjectDto>> QueryAsync(
            XQuery query,
            bool ignoreSoftDeleteds = true,
            Expression<Func<XAiProject, bool>> predicate = null,
            Func<IQueryable<XAiProject>, IOrderedQueryable<XAiProject>> orderBuilder = null,
            Func<IQueryable<XAiProject>, IIncludableQueryable<XAiProject, object>> includeBuilder = null,
            bool fillConversations = true,
            bool fillConversationMessages = true,
            CancellationToken cancellationToken = default
        )
        {
            //
            var result = await base.QueryAsync(
                query: query,
                predicate: predicate,
                orderBuilder: orderBuilder,
                includeBuilder: includeBuilder,
                cancellationToken: cancellationToken,
                ignoreSoftDeleteds: ignoreSoftDeleteds
            );

            //
            // Do Enrichment if result Exists ...
            if (!result.IsNullOrDefault())
            {
                //
                await enricher.EnrichRangeAsync(
                    sources: result.Items,
                    destinations: result.Items,
                    fillConversations: fillConversations,
                    cancellationToken: cancellationToken,
                    fillConversationMessages: fillConversationMessages
                );
            }

            //
            return result;
        }
        #endregion
        #endregion
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Dtos\XAiProjectServiceProvider.cs

using System;
using Microsoft.AspNetCore.SignalR;
using xAiModels.Interfaces.Dtos;
using xAiModels.Models.Dtos;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xIdentityService.Interfaces;
using xPushService.Base;

namespace xAiModels.Providers.Dtos
{
    public class XAiProjectServiceProvider : XBaseDtoProvider<XAiProject, XAiProjectDto, Guid, XAiProjectDtoHub>, IXAiProjectServiceProvider
    {
        public XAiProjectServiceProvider(
            IHubContext<XAiProjectDtoHub> hub,
            XDataServiceConfiguration dataConfiguration,
            IXAiProjectRepositoryService service,
            IXIdentityProvider identityProvider = null
        ) : base(
            hub,
            dataConfiguration,
            service,
            identityProvider
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Enrichers\XAiConversationEnricher.cs

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using xAiModels.Interfaces;
using xAiModels.Interfaces.Dtos;
using xAiModels.Interfaces.Enrichers;
using xAiModels.Models.Dtos;
using xCommons.Extensions;
using xIdentityService.Interfaces;
using xIdentityService.Extensions;
using xModels.Dtos;
using System.Linq;

namespace xAiModels.Providers.Enrichers
{
    public class XAiConversationEnricher : IXAiConversationEnricher
    {
        private readonly IXIdentityProvider identityProvider;
        private readonly IXAiProjectRepositoryService projectRepositoryService;
        private readonly IXAiConversationTitleResourceProvider conversationTitleResourceProvider;

        public XAiConversationEnricher(
            IXIdentityProvider identityProvider,
            IXAiProjectRepositoryService projectRepositoryService,
            IXAiConversationTitleResourceProvider conversationTitleResourceProvider
        )
        {
            //
            this.identityProvider = identityProvider;
            this.projectRepositoryService = projectRepositoryService;
            this.conversationTitleResourceProvider = conversationTitleResourceProvider;
        }

        /// <summary>
        /// Converts a Signle Entity to Dto ...
        /// </summary>
        /// <param name="source"></param>
        /// <param name="destination"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task EnrichAsync(
            XAiConversationDto source,
            XAiConversationDto destination,
            CancellationToken cancellationToken = default
        )
        {
            await EnrichAsync(
                source: source,
                fillMessages: false,
                destination: destination,
                cancellationToken: cancellationToken
            );
        }

        /// <summary>
        /// Converts a Collection of Entities to Dtos ...
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="destinations"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task EnrichRangeAsync(
            IEnumerable<XAiConversationDto> sources,
            IEnumerable<XAiConversationDto> destinations,
            CancellationToken cancellationToken = default
        )
        {
            await EnrichRangeAsync(
                sources: sources,
                fillMessages: false,
                destinations: destinations,
                cancellationToken: cancellationToken
            );
        }

        /// <summary>
        /// Converts a Signle Entity to Dto ...
        /// </summary>
        /// <param name="source"></param>
        /// <param name="destination"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task EnrichAsync(
            XAiConversationDto source,
            XAiConversationDto destination,
            bool fillMessages = false,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Run independent async operations concurrently
            var ownerTask = ResolveOwnerAsync(source.OwnerId, cancellationToken);
            var projectTask = ResolveProjectAsync(source.ProjectId, cancellationToken);
            var titleLocalesTask = ResolveTitleLocalesAsync(source.Title, cancellationToken);
            var messagesTask = ResolveMessagesAsync(source.Id, cancellationToken);

            //
            // wait until All Tasks Resolved ... 
            Task[] tasks = [
                ownerTask,
                projectTask,
                titleLocalesTask
            ];
            if (fillMessages)
            {
                tasks = [.. tasks, messagesTask];
            }
            await Task.WhenAll(tasks);

            //
            // Attached Resolved Info to Destination ...
            destination.Owner = ownerTask.Result;
            destination.TitleResource = titleLocalesTask.Result;
            var project = projectTask.Result;
            if (!project.IsNullOrDefault())
            {
                destination.ProjectTitle = project.Title;
                destination.ProjectDescription = project.Description;
                destination.ProjectTitleResource = project.TitleResource;
                destination.ProjectDescriptionResource = project.DescriptionResource;
            }
            if (!messagesTask.Result.IsNull())
            {
                destination.Messages = [.. messagesTask.Result];
            }
        }

        /// <summary>
        /// Converts a Collection of Entities to Dtos ...
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="destinations"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task EnrichRangeAsync(
            IEnumerable<XAiConversationDto> sources,
            IEnumerable<XAiConversationDto> destinations,
            bool fillMessages = false,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Make Tasks Ready ...
            var tasks = sources.Zip(destinations, (src, dst) =>
                EnrichAsync(src, dst, cancellationToken)
            );

            //
            await Task.WhenAll(tasks);
        }

        //
        #region Private ...
        /// <summary>
        /// Reading Owner User Info ...
        /// </summary>
        /// <param name="ownerId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        private async Task<XPersonDto> ResolveOwnerAsync(
            string ownerId,
            CancellationToken cancellationToken = default
        )
        {
            //
            XPersonDto result = null;

            //
            // Validate ...
            if (ownerId.IsNullOrEmpty())
            {
                return result;
            }

            //
            // Try to Retrieve User Info ...
            var device = identityProvider.GetDevice();
            var userInfo = await identityProvider.GetUserInfo(
                device: device,
                userSelectByParam: ownerId
            );
            result = userInfo.ToXPersonDto();

            //
            return result;
        }

        /// <summary>
        /// Reading Title Locales ...
        /// </summary>
        /// <param name="title"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        private async Task<XResourceDto> ResolveTitleLocalesAsync(
            string title,
            CancellationToken cancellationToken = default
        )
        {
            //
            XResourceDto result = null;
            if (title.IsNullOrEmpty())
            {
                return result;
            }

            //
            result = await conversationTitleResourceProvider.GetResource(
                owner: null,
                identifier: title,
                cancellationToken: cancellationToken
            );

            //
            return result;
        }

        /// <summary>
        /// Retrieve Project ...
        /// </summary>
        /// <param name="projectId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        private async Task<XAiProjectDto> ResolveProjectAsync(
            Guid projectId,
            CancellationToken cancellationToken = default
        )
        {
            //
            XAiProjectDto result = null;
            if (projectId.IsNull() || projectId.IsDefaultGuid())
            {
                return result;
            }

            //
            result = await projectRepositoryService.GetAsync(
                id: projectId,
                includeBuilder: null,
                ignoreSoftDeleteds: true,
                cancellationToken: cancellationToken
            );

            //
            return result;
        }

        /// <summary>
        /// Retrieve Specified Conversation Messages ...
        /// </summary>
        /// <param name="conversationId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        private async Task<IEnumerable<XAiMessageDto>> ResolveMessagesAsync(
            Guid conversationId,
            CancellationToken cancellationToken = default
        )
        {
            //
            var result = new List<XAiMessageDto>();

            //
            if (conversationId.IsDefaultGuid())
            {
                return result;
            }

            //
            return result;
        }
        #endregion
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Enrichers\XAiMessageEnricher.cs

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using xAiModels.Interfaces.Enrichers;
using xAiModels.Models.Dtos;
using xCommons.Extensions;
using xIdentityService.Interfaces;
using xIdentityService.Extensions;
using xModels.Dtos;
using System.Linq;
using xAiModels.Interfaces.Dtos;

namespace xAiModels.Providers.Enrichers
{
    public class XAiMessageEnricher : IXAiMessageEnricher
    {
        private readonly IXIdentityProvider identityProvider;
        private readonly IXAiConversationRepositoryService conversationRepositoryService;

        public XAiMessageEnricher(
            IXIdentityProvider identityProvider,
            IXAiConversationRepositoryService conversationRepositoryService
        )
        {
            this.identityProvider = identityProvider;
            this.conversationRepositoryService = conversationRepositoryService;
        }

        public async Task EnrichAsync(
            XAiMessageDto source,
            XAiMessageDto destination,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Run independent async operations concurrently
            var ownerTask = ResolveOwnerAsync(source.OwnerId, cancellationToken);
            var conversationTask = ResolveConversationAsync(source.ConversationId, cancellationToken);
            
            //
            // wait until All Tasks Resolved ... 
            await Task.WhenAll(ownerTask, conversationTask);

            //
            // Attached Resolved Info to Destination ...
            destination.Owner = ownerTask.Result;
            if (!conversationTask.Result.IsNullOrDefault())
            {
                //
                destination.ConversationTitle = conversationTask.Result.Title;
                destination.ConversationTitleResouce = conversationTask.Result.TitleResource;
            }
        }

        public async Task EnrichRangeAsync(
            IEnumerable<XAiMessageDto> sources,
            IEnumerable<XAiMessageDto> destinations,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Make Tasks Ready ...
            var tasks = sources.Zip(destinations, (src, dst) =>
                EnrichAsync(src, dst, cancellationToken)
            );

            //
            await Task.WhenAll(tasks);
        }

        //
        #region Private ...
        /// <summary>
        /// Reading Owner User Info ...
        /// </summary>
        /// <param name="ownerId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        private async Task<XPersonDto> ResolveOwnerAsync(
            string ownerId,
            CancellationToken cancellationToken = default
        )
        {
            //
            XPersonDto result = null;

            //
            // Validate ...
            if (ownerId.IsNullOrEmpty())
            {
                return result;
            }

            //
            // Try to Retrieve User Info ...
            var device = identityProvider.GetDevice();
            var userInfo = await identityProvider.GetUserInfo(
                device: device,
                userSelectByParam: ownerId
            );
            result = userInfo.ToXPersonDto();

            //
            return result;
        }

        /// <summary>
        /// Reading Conversation ...
        /// </summary>
        /// <param name="conversationId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        private async Task<XAiConversationDto> ResolveConversationAsync(
            Guid conversationId,
            CancellationToken cancellationToken = default
        )
        {
            //
            XAiConversationDto result = null;

            //
            // Validate ...
            if (conversationId.IsNull() ||
                conversationId.IsDefaultGuid()
            )
            {
                return result;
            }

            //
            result = await conversationRepositoryService.GetAsync(
                id: conversationId,
                includeBuilder: null,
                ignoreSoftDeleteds: true,
                cancellationToken: cancellationToken
            );

            //
            return result;
        }
        #endregion
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Enrichers\XAiProjectEnricher.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using xAiModels.Interfaces.Enrichers;
using xAiModels.Models.Dtos;
using xCommons.Extensions;
using xIdentityService.Interfaces;
using xIdentityService.Extensions;
using xModels.Dtos;
using xAiModels.Interfaces;
using xAiModels.Interfaces.Dtos;

namespace xAiModels.Providers.Enrichers
{
    public class XAiProjectEnricher : IXAiProjectEnricher
    {
        private readonly IXIdentityProvider identityProvider;
        private readonly IXAiProjectTitleResourceProvider projectTitleResourceProvider;
        // private readonly IXAiConversationRepositoryService conversationRepositoryService;
        private readonly IXAiProjectDescriptionResourceProvider projectDescriptionResourceProvider;

        public XAiProjectEnricher(
            IXIdentityProvider identityProvider,
            IXAiProjectTitleResourceProvider projectTitleResourceProvider,
            // IXAiConversationRepositoryService conversationRepositoryService,
            IXAiProjectDescriptionResourceProvider projectDescriptionResourceProvider
        )
        {
            this.identityProvider = identityProvider;
            this.projectTitleResourceProvider = projectTitleResourceProvider;
            // this.conversationRepositoryService = conversationRepositoryService;
            this.projectDescriptionResourceProvider = projectDescriptionResourceProvider;
        }

        public async Task EnrichAsync(
            XAiProjectDto source,
            XAiProjectDto destination,
            CancellationToken cancellationToken = default
        )
        {
            await EnrichAsync(
                source: source,
                destination: destination,
                fillConversations: false,
                cancellationToken: cancellationToken
            );
        }

        public async Task EnrichAsync(
            XAiProjectDto source,
            XAiProjectDto destination,
            bool fillConversations = true,
            bool fillConversationMessages = true,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Run independent async operations concurrently
            var conversationsTask = ResolveConversationsAsync(
                source.Id, 
                fillConversationMessages, 
                cancellationToken
            );
            var titleTask = ResolveTitleAsync(source.Title, cancellationToken);
            var ownerTask = ResolveOwnerAsync(source.OwnerId, cancellationToken);
            var descriptionTask = ResolveDescriptionAsync(source.Description, cancellationToken);

            //
            // wait until All Tasks Resolved ... 
            Task[] tasks =
            {
                titleTask,
                ownerTask,
                descriptionTask
            };
            if (fillConversations)
            {
                tasks = [.. tasks, conversationsTask];
            }
            await Task.WhenAll(tasks);

            //
            // Attached Resolved Info to Destination ...
            destination.Owner = ownerTask.Result;
            destination.TitleResource = titleTask.Result;
            destination.DescriptionResource = descriptionTask.Result;
            if (!conversationsTask.Result.IsNull())
            {
                destination.Conversations = [.. conversationsTask.Result];
            }
        }

        public async Task EnrichRangeAsync(
            IEnumerable<XAiProjectDto> sources,
            IEnumerable<XAiProjectDto> destinations,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Make Tasks Ready ...
            var tasks = sources.Zip(destinations, (src, dst) =>
                EnrichAsync(src, dst, cancellationToken)
            );

            //
            await Task.WhenAll(tasks);
        }

        public async Task EnrichRangeAsync(
            IEnumerable<XAiProjectDto> sources,
            IEnumerable<XAiProjectDto> destinations,
            bool fillConversations = true,
            bool fillConversationMessages = true,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Make Tasks Ready ...
            var tasks = sources.Zip(destinations, (src, dst) =>
                EnrichAsync(
                    source: src, 
                    destination: dst,  
                    fillConversations: fillConversations, 
                    cancellationToken: cancellationToken,
                    fillConversationMessages: fillConversationMessages
                )
            );

            //
            await Task.WhenAll(tasks);
        }

        //
        #region Private ...
        /// <summary>
        /// Reading Owner User Info ...
        /// </summary>
        /// <param name="ownerId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        private async Task<XPersonDto> ResolveOwnerAsync(
            string ownerId,
            CancellationToken cancellationToken = default
        )
        {
            //
            XPersonDto result = null;

            //
            // Validate ...
            if (ownerId.IsNullOrEmpty())
            {
                return result;
            }

            //
            // Try to Retrieve User Info ...
            var device = identityProvider.GetDevice();
            var userInfo = await identityProvider.GetUserInfo(
                device: device,
                userSelectByParam: ownerId
            );
            result = userInfo.ToXPersonDto();

            //
            return result;
        }

        /// <summary>
        /// Retrieve Title Resource ...
        /// </summary>
        /// <param name="identifier"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        private async Task<XResourceDto> ResolveTitleAsync(
            string identifier,
            CancellationToken cancellationToken = default
        )
        {
            //
            XResourceDto result = null;

            //
            // Validate ...
            if (identifier.IsNullOrEmpty())
            {
                return result;
            }

            //
            result = await projectTitleResourceProvider.GetResource(
                owner: null,
                identifier: identifier,
                cancellationToken: cancellationToken
            );

            //
            return result;
        }

        /// <summary>
        /// Retrieve Description Resource ...
        /// </summary>
        /// <param name="identifier"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        private async Task<XResourceDto> ResolveDescriptionAsync(
            string identifier,
            CancellationToken cancellationToken = default
        )
        {
            //
            XResourceDto result = null;

            //
            // Validate ...
            if (identifier.IsNullOrEmpty())
            {
                return result;
            }

            //
            result = await projectDescriptionResourceProvider.GetResource(
                owner: null,
                identifier: identifier,
                cancellationToken: cancellationToken
            );

            //
            return result;
        }

        /// <summary>
        /// Retrieve Project Conversations ...
        /// </summary>
        /// <param name="projectId"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        private async Task<List<XAiConversationDto>> ResolveConversationsAsync(
            Guid projectId,
            bool fillMessages = false,
            CancellationToken cancellationToken = default
        )
        {
            //
            List<XAiConversationDto> result = [];

            //
            // Validate ...
            if (projectId.IsNull() ||
                projectId.IsDefaultGuid()
            )
            {
                return result;
            }

            //
            // var items = await conversationRepositoryService.FindManyAsync(
            //     orderBuilder: null,
            //     includeBuilder: null,
            //     ignoreSoftDeleteds: true,
            //     fillMessages: fillMessages,
            //     predicate: x => x.Id == projectId,
            //     cancellationToken: cancellationToken
            // );
            // result = [.. items];

            //
            return result;
        }
        #endregion
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Entities\XAiConversationEFRepository.cs

using System;
using xAiModels.Interfaces.Entities;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xDataService.Db;
using xDataService.Interfaces;
using xDataService.Providers;

namespace xAiModels.Providers.Entities
{
    public class XAiConversationEFRepository<TContext> : XBaseEFRepository<XAiConversation, Guid, TContext>, IXAiConversationRepository
    where TContext : XDbContext
    {
        public XAiConversationEFRepository(
            IXUnitOfWorks<TContext> unitOfWorks,
            XDataServiceConfiguration configuration,
            IXAiConversationKeyGenerator keyGenerator = null,
            IXAiConversationRepositoryEvents baseRepositoryEvents = null
        ) : base(
            unitOfWorks,
            configuration,
            keyGenerator,
            baseRepositoryEvents
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Entities\XAiConversationEntityHub.cs

using System;
using Microsoft.Extensions.Logging;
using xAiModels.Interfaces.Entities;
using xAiModels.Models.Entities;
using xPushService.Base;

namespace xAiModels.Providers.Entities
{
    public class XAiConversationEntityHub : XBaseEntityHub<XAiConversation, Guid>, IXAiConversationEntityHub
    {
        public XAiConversationEntityHub(
            ILogger<XAiConversationEntityHub> logger
        ) : base(logger)
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Entities\XAiConversationInMemoryRepository.cs

using System;
using xAiModels.Interfaces.Entities;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xDataService.Providers;

namespace xAiModels.Providers.Entities
{
    public class XAiConversationInMemoryRepository : XBaseInMemoryRepository<XAiConversation, Guid>, IXAiConversationRepository
    {
        public XAiConversationInMemoryRepository(
            XDataServiceConfiguration configuration,
            IXAiConversationKeyGenerator keyGenerator = null,
            IXAiConversationRepositoryEvents baseRepositoryEvents = null
        ) : base(
            configuration,
            keyGenerator,
            baseRepositoryEvents
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Entities\XAiConversationKeyGenerator.cs

using xAiModels.Interfaces.Entities;
using xAiModels.Models.Entities;
using xDataService.Providers;

namespace xAiModels.Providers.Entities
{
    public class XAiConversationKeyGenerator : XGuidKeyGenerator<XAiConversation>, IXAiConversationKeyGenerator
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Entities\XAiConversationMongoRepository.cs

using System;
using xAiModels.Interfaces.Entities;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xDataService.Providers;

namespace xAiModels.Providers.Entities
{
    public class XAiConversationMongoRepository : XBaseMongoRepository<XAiConversation, Guid>, IXAiConversationRepository
    {
        public XAiConversationMongoRepository(
            XDataBaseConfiguration dbConfiguration,
            XDataServiceConfiguration configuration,
            string collectionName = null,
            IXAiConversationKeyGenerator keyGenerator = null,
            IXAiConversationRepositoryEvents baseRepositoryEvents = null
        ) : base(
            dbConfiguration,
            configuration,
            collectionName,
            keyGenerator,
            baseRepositoryEvents
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Entities\XAiConversationRepositoryEvents.cs

using xAiModels.Interfaces.Entities;
using xAiModels.Models.Entities;
using xDataService.Providers;

namespace xAiModels.Providers.Entities
{
    public class XAiConversationRepositoryEvents : XBaseRepositoryEvents<XAiConversation>, IXAiConversationRepositoryEvents
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Entities\XAiConversationRepositoryProvider.cs

using System;
using Microsoft.AspNetCore.SignalR;
using xAiModels.Interfaces.Entities;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xIdentityService.Interfaces;
using xPushService.Base;

namespace xAiModels.Providers.Entities
{
    public class XAiConversationRepositoryProvider : XBaseEntityProvider<XAiConversation, Guid, XAiConversationEntityHub>, IXAiConversationRepositoryProvider
    {
        public XAiConversationRepositoryProvider(
            IHubContext<XAiConversationEntityHub> hub,
            IXAiConversationRepository repository,
            XDataServiceConfiguration dataConfiguration,
            IXIdentityProvider identityProvider = null
        ) : base(
            hub,
            repository,
            dataConfiguration,
            identityProvider
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Entities\XAiConversationSeederBase.cs

using System;
using xAiModels.Interfaces.Entities;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xDataService.Providers;

namespace xAiModels.Providers.Entities
{
    public abstract class XAiConversationSeederBase : XBaseDbSeeder<XAiConversation, Guid>, IXAiConversationSeeder
    {
        protected XAiConversationSeederBase(
            string entity,
            string database,
            IXAiConversationRepository repository,
            XDataServiceConfiguration dataServiceConfiguration
        ) : base(
            entity,
            database,
            repository,
            dataServiceConfiguration
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Entities\XAiMessageEFRepository.cs

using System;
using xAiModels.Interfaces.Entities;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xDataService.Db;
using xDataService.Interfaces;
using xDataService.Providers;

namespace xAiModels.Providers.Entities
{
    public class XAiMessageEFRepository<TContext> : XBaseEFRepository<XAiMessage, Guid, TContext>, IXAiMessageRepository
    where TContext : XDbContext
    {
        public XAiMessageEFRepository(
            IXUnitOfWorks<TContext> unitOfWorks,
            XDataServiceConfiguration configuration,
            IXAiMessageKeyGenerator keyGenerator = null,
            IXAiMessageRepositoryEvents baseRepositoryEvents = null
        ) : base(
            unitOfWorks,
            configuration,
            keyGenerator,
            baseRepositoryEvents
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Entities\XAiMessageEntityHub.cs

using System;
using Microsoft.Extensions.Logging;
using xAiModels.Interfaces.Entities;
using xAiModels.Models.Entities;
using xPushService.Base;

namespace xAiModels.Providers.Entities
{
    public class XAiMessageEntityHub : XBaseEntityHub<XAiMessage, Guid>, IXAiMessageEntityHub
    {
        public XAiMessageEntityHub(
            ILogger<XAiMessageEntityHub> logger
        ) : base(logger)
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Entities\XAiMessageInMemoryRepository.cs

using System;
using xAiModels.Interfaces.Entities;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xDataService.Providers;

namespace xAiModels.Providers.Entities
{
    public class XAiMessageInMemoryRepository : XBaseInMemoryRepository<XAiMessage, Guid>, IXAiMessageRepository
    {
        public XAiMessageInMemoryRepository(
            XDataServiceConfiguration configuration,
            IXAiMessageKeyGenerator keyGenerator = null,
            IXAiMessageRepositoryEvents baseRepositoryEvents = null
        ) : base(
            configuration,
            keyGenerator,
            baseRepositoryEvents
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Entities\XAiMessageKeyGenerator.cs

using xAiModels.Interfaces.Entities;
using xAiModels.Models.Entities;
using xDataService.Providers;

namespace xAiModels.Providers.Entities
{
    public class XAiMessageKeyGenerator : XGuidKeyGenerator<XAiMessage>, IXAiMessageKeyGenerator
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Entities\XAiMessageMongoRepository.cs

using System;
using xAiModels.Interfaces.Entities;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xDataService.Providers;

namespace xAiModels.Providers.Entities
{
    public class XAiMessageMongoRepository : XBaseMongoRepository<XAiMessage, Guid>, IXAiMessageRepository
    {
        public XAiMessageMongoRepository(
            XDataBaseConfiguration dbConfiguration,
            XDataServiceConfiguration configuration,
            string collectionName = null,
            IXAiMessageKeyGenerator keyGenerator = null,
            IXAiMessageRepositoryEvents baseRepositoryEvents = null
        ) : base(
            dbConfiguration,
            configuration,
            collectionName,
            keyGenerator,
            baseRepositoryEvents
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Entities\XAiMessageRepositoryEvents.cs

using xAiModels.Interfaces.Entities;
using xAiModels.Models.Entities;
using xDataService.Providers;

namespace xAiModels.Providers.Entities
{
    public class XAiMessageRepositoryEvents : XBaseRepositoryEvents<XAiMessage>, IXAiMessageRepositoryEvents
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Entities\XAiMessageRepositoryProvider.cs

using System;
using Microsoft.AspNetCore.SignalR;
using xAiModels.Interfaces.Entities;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xIdentityService.Interfaces;
using xPushService.Base;

namespace xAiModels.Providers.Entities
{
    public class XAiMessageRepositoryProvider : XBaseEntityProvider<XAiMessage, Guid, XAiMessageEntityHub>, IXAiMessageRepositoryProvider
    {
        public XAiMessageRepositoryProvider(
            IHubContext<XAiMessageEntityHub> hub,
            IXAiMessageRepository repository,
            XDataServiceConfiguration dataConfiguration,
            IXIdentityProvider identityProvider = null
        ) : base(
            hub,
            repository,
            dataConfiguration,
            identityProvider
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Entities\XAiMessageSeederBase.cs

using System;
using xAiModels.Interfaces.Entities;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xDataService.Providers;

namespace xAiModels.Providers.Entities
{
    public abstract class XAiMessageSeederBase : XBaseDbSeeder<XAiMessage, Guid>, IXAiMessageSeeder
    {
        protected XAiMessageSeederBase(
            string entity,
            string database,
            IXAiMessageRepository repository,
            XDataServiceConfiguration dataServiceConfiguration
        ) : base(
            entity,
            database,
            repository,
            dataServiceConfiguration
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Entities\XAiProjectEFRepository.cs

using System;
using xAiModels.Interfaces.Entities;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xDataService.Db;
using xDataService.Interfaces;
using xDataService.Providers;

namespace xAiModels.Providers.Entities
{
    public class XAiProjectEFRepository<TContext> : XBaseEFRepository<XAiProject, Guid, TContext>, IXAiProjectRepository
    where TContext : XDbContext
    {
        public XAiProjectEFRepository(
            IXUnitOfWorks<TContext> unitOfWorks,
            XDataServiceConfiguration configuration,
            IXAiProjectKeyGenerator keyGenerator = null,
            IXAiProjectRepositoryEvents baseRepositoryEvents = null
        ) : base(
            unitOfWorks,
            configuration,
            keyGenerator,
            baseRepositoryEvents
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Entities\XAiProjectEntityHub.cs

using System;
using Microsoft.Extensions.Logging;
using xAiModels.Interfaces.Entities;
using xAiModels.Models.Entities;
using xPushService.Base;

namespace xAiModels.Providers.Entities
{
    public class XAiProjectEntityHub : XBaseEntityHub<XAiProject, Guid>, IXAiProjectEntityHub
    {
        public XAiProjectEntityHub(
            ILogger<XAiProjectEntityHub> logger
        ) : base(logger)
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Entities\XAiProjectInMemoryRepository.cs

using System;
using xAiModels.Interfaces.Entities;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xDataService.Providers;

namespace xAiModels.Providers.Entities
{
    public class XAiProjectInMemoryRepository : XBaseInMemoryRepository<XAiProject, Guid>, IXAiProjectRepository
    {
        public XAiProjectInMemoryRepository(
            XDataServiceConfiguration configuration,
            IXAiProjectKeyGenerator keyGenerator = null,
            IXAiProjectRepositoryEvents baseRepositoryEvents = null
        ) : base(
            configuration,
            keyGenerator,
            baseRepositoryEvents
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Entities\XAiProjectKeyGenerator.cs

using xAiModels.Interfaces.Entities;
using xAiModels.Models.Entities;
using xDataService.Providers;

namespace xAiModels.Providers.Entities
{
    public class XAiProjectKeyGenerator : XGuidKeyGenerator<XAiProject>, IXAiProjectKeyGenerator
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Entities\XAiProjectMongoRepository.cs

using System;
using xAiModels.Interfaces.Entities;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xDataService.Providers;

namespace xAiModels.Providers.Entities
{
    public class XAiProjectMongoRepository : XBaseMongoRepository<XAiProject, Guid>, IXAiProjectRepository
    {
        public XAiProjectMongoRepository(
            XDataBaseConfiguration dbConfiguration,
            XDataServiceConfiguration configuration,
            string collectionName = null,
            IXAiProjectKeyGenerator keyGenerator = null,
            IXAiProjectRepositoryEvents baseRepositoryEvents = null
        ) : base(
            dbConfiguration,
            configuration,
            collectionName,
            keyGenerator,
            baseRepositoryEvents
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Entities\XAiProjectRepositoryEvents.cs

using xAiModels.Interfaces.Entities;
using xAiModels.Models.Entities;
using xDataService.Providers;

namespace xAiModels.Providers.Entities
{
    public class XAiProjectRepositoryEvents : XBaseRepositoryEvents<XAiProject>, IXAiProjectRepositoryEvents
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Entities\XAiProjectRepositoryProvider.cs

using System;
using Microsoft.AspNetCore.SignalR;
using xAiModels.Interfaces.Entities;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xIdentityService.Interfaces;
using xPushService.Base;

namespace xAiModels.Providers.Entities
{
    public class XAiProjectRepositoryProvider : XBaseEntityProvider<XAiProject, Guid, XAiProjectEntityHub>, IXAiProjectRepositoryProvider
    {
        public XAiProjectRepositoryProvider(
            IHubContext<XAiProjectEntityHub> hub,
            IXAiProjectRepository repository,
            XDataServiceConfiguration dataConfiguration,
            IXIdentityProvider identityProvider = null
        ) : base(
            hub,
            repository,
            dataConfiguration,
            identityProvider
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Entities\XAiProjectSeederBase.cs

using System;
using xAiModels.Interfaces.Entities;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xDataService.Providers;

namespace xAiModels.Providers.Entities
{
    public abstract class XAiProjectSeederBase : XBaseDbSeeder<XAiProject, Guid>, IXAiProjectSeeder
    {
        protected XAiProjectSeederBase(
            string entity,
            string database,
            IXAiProjectRepository repository,
            XDataServiceConfiguration dataServiceConfiguration
        ) : base(
            entity,
            database,
            repository,
            dataServiceConfiguration
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\GraphQL\XAiConversationGraphQLTypeHelper.cs

using System;
using xAiModels.Constants;
using xAiModels.Interfaces.Entities;
using xAiModels.Models.Entities;
using xDataService.GraphQL;

namespace xAiModels.Providers.GraphQL
{
    public class XAiConversationGraphQLTypeHelper : XBaseGraphQLTypeHelper<XAiConversation, Guid>, IXAiConversationGraphQLTypeHelper
    {
        public override string GetInQueryCollectionName()
        {
            return XAiModelsConstants.XAiConversationCollectionName;
        }

        public override string GetInQuerySingleName()
        {
            return XAiModelsConstants.XAiConversationSignleName;
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\GraphQL\XAiConversationGraphQuery.cs

using System;
using GraphQL.Types;
using xAiModels.Interfaces.Entities;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xDataService.GraphQL;

namespace xAiModels.Providers.GraphQL
{
    public class XAiConversationGraphQuery : XBaseGraphQLQuery<XAiConversation, Guid, XAiConversationGraphType, GuidGraphType>
    {
        public XAiConversationGraphQuery(
            XDataServiceConfiguration configuration,
            IXAiConversationRepository repository,
            IXAiConversationGraphQLTypeHelper helper
        ) : base(
            configuration,
            repository,
            helper
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\GraphQL\XAiConversationGraphSchema.cs

using System;
using GraphQL.Types;

namespace xAiModels.Providers.GraphQL
{
    public class XAiConversationGraphSchema : Schema
    {
        public XAiConversationGraphSchema(IServiceProvider services) : base(services)
        {
            Query = (XAiConversationGraphQuery)services.GetService(typeof(XAiConversationGraphQuery));
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\GraphQL\XAiConversationGraphType.cs

using System;
using xAiModels.Models.Entities;
using xDataService.GraphQL;

namespace xAiModels.Providers.GraphQL
{
    public class XAiConversationGraphType : XBaseGraphObjectType<XAiConversation, Guid>
    {
        public XAiConversationGraphType() : base()
        {
            Field(x => x.Title);
            Field(x => x.OwnerId);
            Field(x => x.CreatedOn);
            Field(x => x.UpdatedAt);
            Field(x => x.ProjectId);
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\GraphQL\XAiMessageGraphQLTypeHelper.cs

using System;
using xAiModels.Constants;
using xAiModels.Interfaces.Entities;
using xAiModels.Models.Entities;
using xDataService.GraphQL;

namespace xAiModels.Providers.GraphQL
{
    public class XAiMessageGraphQLTypeHelper : XBaseGraphQLTypeHelper<XAiMessage, Guid>, IXAiMessageGraphQLTypeHelper
    {
        public override string GetInQueryCollectionName()
        {
            return XAiModelsConstants.XAiMessageCollectionName;
        }

        public override string GetInQuerySingleName()
        {
            return XAiModelsConstants.XAiMessageSingleName;
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\GraphQL\XAiMessageGraphQuery.cs

using System;
using GraphQL.Types;
using xAiModels.Interfaces.Entities;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xDataService.GraphQL;

namespace xAiModels.Providers.GraphQL
{
    public class XAiMessageGraphQuery : XBaseGraphQLQuery<XAiMessage, Guid, XAiMessageGraphType, GuidGraphType>
    {
        public XAiMessageGraphQuery(
            XDataServiceConfiguration configuration,
            IXAiMessageRepository repository,
            IXAiMessageGraphQLTypeHelper helper
        ) : base(
            configuration,
            repository,
            helper
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\GraphQL\XAiMessageGraphSchema.cs

using System;
using GraphQL.Types;

namespace xAiModels.Providers.GraphQL
{
    public class XAiMessageGraphSchema : Schema
    {
        public XAiMessageGraphSchema(IServiceProvider services) : base(services)
        {
            Query = (XAiMessageGraphQuery)services.GetService(typeof(XAiMessageGraphQuery));
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\GraphQL\XAiMessageGraphType.cs

using System;
using xAiModels.Models.Entities;
using xDataService.GraphQL;

namespace xAiModels.Providers.GraphQL
{
    public class XAiMessageGraphType: XBaseGraphObjectType<XAiMessage, Guid>
    {
        public XAiMessageGraphType() : base()
        {
            Field(x => x.Role);
            Field(x => x.OwnerId);
            Field(x => x.Content);
            Field(x => x.Metadata);
            Field(x => x.CreatedOn);
            Field(x => x.UpdatedAt);
            Field(x => x.ConversationId);
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\GraphQL\XAiProjectGraphQLTypeHelper.cs

using System;
using xAiModels.Constants;
using xAiModels.Interfaces.Entities;
using xAiModels.Models.Entities;
using xDataService.GraphQL;

namespace xAiModels.Providers.GraphQL
{
    public class XAiProjectGraphQLTypeHelper : XBaseGraphQLTypeHelper<XAiProject, Guid>, IXAiProjectGraphQLTypeHelper
    {
        public override string GetInQueryCollectionName()
        {
            return XAiModelsConstants.XAiProjectCollectionName;
        }

        public override string GetInQuerySingleName()
        {
            return XAiModelsConstants.XAiProjectSingleName;
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\GraphQL\XAiProjectGraphQuery.cs

using System;
using GraphQL.Types;
using xAiModels.Interfaces.Entities;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xDataService.GraphQL;

namespace xAiModels.Providers.GraphQL
{
    public class XAiProjectGraphQuery : XBaseGraphQLQuery<XAiProject, Guid, XAiProjectGraphType, GuidGraphType>
    {
        public XAiProjectGraphQuery(
            XDataServiceConfiguration configuration,
            IXAiProjectRepository repository,
            IXAiProjectGraphQLTypeHelper helper
        ) : base(
            configuration,
            repository,
            helper
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\GraphQL\XAiProjectGraphSchema.cs

using System;
using GraphQL.Types;

namespace xAiModels.Providers.GraphQL
{
    public class XAiProjectGraphSchema : Schema
    {
        public XAiProjectGraphSchema(IServiceProvider services) : base(services)
        {
            Query = (XAiProjectGraphQuery)services.GetService(typeof(XAiProjectGraphQuery));
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\GraphQL\XAiProjectGraphType.cs

using System;
using xAiModels.Models.Entities;
using xDataService.GraphQL;

namespace xAiModels.Providers.GraphQL
{
    public class XAiProjectGraphType : XBaseGraphObjectType<XAiProject, Guid>
    {
        public XAiProjectGraphType() : base()
        {
            Field(x => x.Title);
            Field(x => x.OwnerId);
            Field(x => x.Prompt);
            Field(x => x.CreatedOn);
            Field(x => x.UpdatedAt);
            Field(x => x.Description);
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiModelsBkp\xAiModels\Providers\Hubs\XAiDataProviderHub.cs

using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using xAiModels.Constants;
using xAiModels.Models.Dtos;
using xCommons.Extensions;
using xPushService.Base;

namespace xAiModels.Providers.Hubs
{
    public class XAiDataProviderHub : XBaseHub
    {
        public XAiDataProviderHub(
            ILogger<XBaseHub> logger
        ) : base(logger)
        { }

        //
        #region XAiProject ...
        /// <summary>
        /// Notify a New Item Created ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task ProjectCreated(
            XAiProjectDto item,
            CancellationToken cancellationToken = default
        )
        {
            //
            var connectionId = Context.ConnectionId;
            if (!connectionId.IsNullOrEmpty())
            {
                //
                await Clients
                    .AllExcept(connectionId)
                    .SendAsync(
                        XAiDataProviderHubActions.ProjectCreated.GetStringValue(),
                        item.ToJSON(camelCase: true),
                        connectionId,
                        cancellationToken
                    );
            }
        }

        /// <summary>
        /// Notify an Item Removed ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task ProjectRemoved(
            XAiProjectDto item,
            CancellationToken cancellationToken = default
        )
        {
            //
            var connectionId = Context.ConnectionId;
            if (!connectionId.IsNullOrEmpty())
            {
                //
                await Clients
                    .AllExcept(connectionId)
                    .SendAsync(
                        XAiDataProviderHubActions.ProjectRemoved.GetStringValue(),
                        item.ToJSON(camelCase: true),
                        connectionId,
                        cancellationToken
                    );
            }
        }

        /// <summary>
        /// Notify an Item Updated ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task ProjectUpdated(
            XAiProjectDto item,
            CancellationToken cancellationToken = default
        )
        {
            //
            var connectionId = Context.ConnectionId;
            if (!connectionId.IsNullOrEmpty())
            {
                //
                await Clients
                    .AllExcept(connectionId)
                    .SendAsync(
                        XAiDataProviderHubActions.ProjectUpdated.GetStringValue(),
                        item.ToJSON(camelCase: true),
                        connectionId,
                        cancellationToken
                    );
            }
        }
        #endregion

        //
        #region XAiConversation ...
        /// <summary>
        /// Notify a New Item Created ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task ConversationCreated(
            XAiProjectDto item,
            CancellationToken cancellationToken = default
        )
        {
            //
            var connectionId = Context.ConnectionId;
            if (!connectionId.IsNullOrEmpty())
            {
                //
                await Clients
                    .AllExcept(connectionId)
                    .SendAsync(
                        XAiDataProviderHubActions.ConversationCreated.GetStringValue(),
                        item.ToJSON(camelCase: true),
                        connectionId,
                        cancellationToken
                    );
            }
        }

        /// <summary>
        /// Notify an Item Removed ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task ConversationRemoved(
            XAiProjectDto item,
            CancellationToken cancellationToken = default
        )
        {
            //
            var connectionId = Context.ConnectionId;
            if (!connectionId.IsNullOrEmpty())
            {
                //
                await Clients
                    .AllExcept(connectionId)
                    .SendAsync(
                        XAiDataProviderHubActions.ConversationRemoved.GetStringValue(),
                        item.ToJSON(camelCase: true),
                        connectionId,
                        cancellationToken
                    );
            }
        }

        /// <summary>
        /// Notify an Item Updated ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task ConversationUpdated(
            XAiProjectDto item,
            CancellationToken cancellationToken = default
        )
        {
            //
            var connectionId = Context.ConnectionId;
            if (!connectionId.IsNullOrEmpty())
            {
                //
                await Clients
                    .AllExcept(connectionId)
                    .SendAsync(
                        XAiDataProviderHubActions.ConversationUpdated.GetStringValue(),
                        item.ToJSON(camelCase: true),
                        connectionId,
                        cancellationToken
                    );
            }
        }
        #endregion

        //
        #region XAiMessage ...
        /// <summary>
        /// Notify a New Item Created ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task MessageCreated(
            XAiProjectDto item,
            CancellationToken cancellationToken = default
        )
        {
            //
            var connectionId = Context.ConnectionId;
            if (!connectionId.IsNullOrEmpty())
            {
                //
                await Clients
                    .AllExcept(connectionId)
                    .SendAsync(
                        XAiDataProviderHubActions.MessageCreated.GetStringValue(),
                        item.ToJSON(camelCase: true),
                        connectionId,
                        cancellationToken
                    );
            }
        }

        /// <summary>
        /// Notify an Item Removed ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task MessageRemoved(
            XAiProjectDto item,
            CancellationToken cancellationToken = default
        )
        {
            //
            var connectionId = Context.ConnectionId;
            if (!connectionId.IsNullOrEmpty())
            {
                //
                await Clients
                    .AllExcept(connectionId)
                    .SendAsync(
                        XAiDataProviderHubActions.MessageRemoved.GetStringValue(),
                        item.ToJSON(camelCase: true),
                        connectionId,
                        cancellationToken
                    );
            }
        }

        /// <summary>
        /// Notify an Item Updated ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task MessageUpdated(
            XAiProjectDto item,
            CancellationToken cancellationToken = default
        )
        {
            //
            var connectionId = Context.ConnectionId;
            if (!connectionId.IsNullOrEmpty())
            {
                //
                await Clients
                    .AllExcept(connectionId)
                    .SendAsync(
                        XAiDataProviderHubActions.MessageUpdated.GetStringValue(),
                        item.ToJSON(camelCase: true),
                        connectionId,
                        cancellationToken
                    );
            }
        }
        #endregion
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiServicceBkp\Configurations\XAIServiceConfiguration.cs

namespace xAiService.Configurations
{
    public partial class XAIServiceConfiguration
    {
        public string Url { get; set; }
        public string XPoweredValue { get; set; }
        public int DefaultClientTimeout { get; set; } = 360;
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiServicceBkp\Constants\ConfigurationNodeNames.cs

namespace xAiService.Constants
{
    public partial struct ConfigurationNodeNames
    {
        public const string AI_SERVICE_NODE = "AIServiceConfiguration";
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiServicceBkp\Constants\XAiApiConstants.cs

namespace xAiService.Constants
{
    public struct XAiApiQueryParams
    {
        public const string Prompt = "prompt";
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiServicceBkp\Constants\XAiApiEndpoint.cs

using xExceptions.Attributes;

namespace xAiService.Constants
{
    public enum XAiApiEndpoint
    {
        [StringValue("AskAI")]
        Ask,
        [StringValue("AskAIStream")]
        Stream,
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiServicceBkp\DI\XDIHelperExtension.cs

using System;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using xAiService.Configurations;
using xAiService.Constants;
using xAiService.Extensions;
using xAiService.Interfaces;
using xAiService.Services;
using xCommons.Extensions;
using xExceptions.Constants;

namespace xAiService.DI
{
    public static partial class XDIHelperExtension
    {
        /// <summary>
        /// Extract AI Service Configuration ...
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static XAIServiceConfiguration GetXAIServiceConfiguration(this IConfiguration source)
        {
            //
            XAIServiceConfiguration result = null;

            //
            var section = source.GetSection(ConfigurationNodeNames.AI_SERVICE_NODE);
            if (!section.IsNull())
            {
                result = section.Get<XAIServiceConfiguration>();
            }

            //
            return result;
        }

        /// <summary>
        /// Add AI Service Configuration ...
        /// </summary>
        /// <param name="source"></param>
        /// <param name="configuration"></param>
        public static void AddXAIServiceConfiguration(
            this IServiceCollection source,
            XAIServiceConfiguration configuration
        )
        {
            source.AddSingleton<XAIServiceConfiguration>(configuration);
        }

        /// <summary>
        /// Adding XAiService ...
        /// </summary>
        /// <param name="services"></param>
        /// <param name="configuration"></param>
        /// <param name="lifeTime"></param>
        public static void AddXAIService(
            this IServiceCollection services,
            IConfiguration configuration,
            ServiceLifetime lifeTime = ServiceLifetime.Scoped
        )
        {
            //
            var config = configuration.GetXAIServiceConfiguration();
            AddAIService(
                services,
                config,
                lifeTime
            );
        }

        /// <summary>
        /// Adding XAiService ...
        /// </summary>
        /// <param name="services"></param>
        /// <param name="configuration"></param>
        /// <param name="lifeTime"></param>
        public static void AddXAIService(
            this IServiceCollection services,
            XAIServiceConfiguration configuration,
            ServiceLifetime lifeTime = ServiceLifetime.Scoped
        )
        {
            //
            AddAIService(
                services,
                configuration,
                lifeTime
            );
        }

        //
        #region Private ...
        /// <summary>
        /// a LogTag for Service ...
        /// </summary>
        private static string XLogTag = "XFileService";

        /// <summary>
        /// print a log in Console ...
        /// </summary>
        /// <param name="message"></param>
        private static void Log(string message)
        {
            Console.WriteLine($"{XLogTag} => {message}");
        }

        /// <summary>
        /// Adding Ai Service ...
        /// </summary>
        /// <param name="services"></param>
        /// <param name="configuration"></param>
        /// <param name="lifeTime"></param>
        private static void AddAIService(
            this IServiceCollection services,
            XAIServiceConfiguration configuration,
            ServiceLifetime lifeTime = ServiceLifetime.Singleton
        )
        {
            //
            var exception = XException.InvalidArgs.ToException(); ;

            //
            // Services ...
            var isServicesExists = !services.IsNull();
            if (!isServicesExists)
            {
                //
                Log("AddAIService failed, services not provided ...");
                throw exception;
            }

            //
            // Lifetime ...
            var isLifetimeExists = !lifeTime.IsNull();
            if (!isLifetimeExists)
            {
                //
                Log("AddAIService failed, lifetime not provided ...");
                throw exception;
            }

            //
            // Check Dependencies Exists ...
            var isDependenciesPassed = true;
            if (!isDependenciesPassed)
            {
                //
                Log("AddAIService failed due Dependencies Issues, not provided ...");
                throw exception;
            }

            //
            // Validate Configurations ...
            var isConfigurationExists =
                !configuration.IsNull() &&
                configuration.IsValid();
            if (!isConfigurationExists)
            {
                //
                Log("AddAIService failed due InValid Service Configuration or not provided ...");
                throw exception;
            }

            //
            // Register Configurations ...
            services.AddXAIServiceConfiguration(configuration);

            //
            // Register Services ...
            services.Add(new ServiceDescriptor(typeof(IXAiService), typeof(XAiService), lifeTime));
        }
        #endregion
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiServicceBkp\Extensions\ConfigurationExtensions.cs

using xAiService.Configurations;
using xCommons.Extensions;

namespace xAiService.Extensions
{
    public static class ModelExtensions
    {
        /// <summary>
        /// Validate XAiServiceConfiguration ...
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsValid(this XAIServiceConfiguration source)
        {
            //
            bool result =
                !source.IsNull() &&
                !source.Url.IsNullOrEmpty() &&
                source.Url.IsValidUrl();

            //
            return result;
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiServicceBkp\Extensions\PromptExtensions.cs

using System.Collections.Generic;
using System.Linq;
using xAiService.Models.Dtos;
using xCommons.Extensions;

namespace xAiService.Extensions
{
    public static class PromptExtensions
    {
        /// <summary>
        /// Get Template Params Filled Prompt ...
        /// </summary>
        /// <param name="source"></param>
        /// <param name="params"></param>
        /// <returns></returns>
        public static string GetMessage(
            this XPromptDto source,
            IDictionary<string, string> @params = null
        )
        {
            //
            var result = string.Empty;

            //
            if (!source.IsNullOrDefault() &&
                !source.Prompt.IsNullOrEmpty()
            )
            {
                result = source.Prompt;
            }

            //
            // Check Params Conditions ...
            if (!source.Params.IsNullOrDefault() &&
                source.Params.HasChild() &&
                !@params.IsNullOrDefault() &&
                @params.HasChild()
            )
            {
                //
                // Select Params ...
                var existsParams = source.Params
                    .Where(p => @params.Any(pr => pr.Key == p.Name))
                    .Distinct()
                    .ToList();

                //
                // Replace Selected Params ...
                existsParams
                    .ForEach(pk =>
                    {
                        result = result.Replace(pk.Param, @params[pk.Name]);
                    });
            }

            //
            return result;
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiServicceBkp\Helpers\VectorHelper.cs

using System;
using xCommons.Extensions;

namespace xAiService.Helpers
{
    public static class VectorHelper
    {
        /// <summary>
        /// Normalize a Vector ...
        /// </summary>
        /// <param name="vector"></param>
        public static void Normalize(float[] vector)
        {
            //
            // Validate ...
            if (vector.IsNull() || !vector.HasChild())
            {
                return;
            }

            //
            float sum = 0f;
            foreach (var value in vector)
            {
                sum += value * value;
            }

            //
            float norm = MathF.Sqrt(sum);
            if (norm <= 1e-9f)
            {
                return;
            }

            //
            for (int i = 0; i < vector.Length; i++)
            {
                vector[i] /= norm;
            }
        }

        /// <summary>
        /// Similarity Check ...
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static float CosineSimilarity(float[] a, float[] b)
        {
            //
            if (a.IsNull() || b.IsNull())
            {
                return 0f;
            }

            //
            if (a.Length != b.Length)
            {
                return 0f;
            }

            //
            float dot = 0f;
            float normA = 0f;
            float normB = 0f;
            for (int i = 0; i < a.Length; i++)
            {
                //
                dot += a[i] * b[i];
                normA += a[i] * a[i];
                normB += b[i] * b[i];
            }

            //
            float denominator = MathF.Sqrt(normA) * MathF.Sqrt(normB);
            if (denominator <= 1e-9f)
            {
                return 0f;
            }

            //
            var result = dot / denominator;
            return result;
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiServicceBkp\Helpers\XPromptHelper.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using xAiService.Models.Dtos;
using xCommons.Extensions;

namespace xAiService.Helpers
{
    public static class XPromptHelper
    {
        /// <summary>
        /// Holds Initialized Prompts ...
        /// </summary>
        private static XPromptsDto prompts;

        /// <summary>
        /// Initialize Prompt Helper ...
        /// </summary>
        /// <param name="path"></param>
        /// <exception cref="Exception"></exception>
        /// <exception cref="InvalidDataException"></exception>
        public static void Initialize(string path = "prompts.json")
        {
            //
            // Create full Path and Check File Exists ...
            var fullPath = Path.Combine(AppContext.BaseDirectory, path);
            if (!File.Exists(fullPath))
            {
                throw new Exception($"Prompts Resources not found: {fullPath}");
            }

            //
            // Read File as Json Text ...
            var json = File.ReadAllText(fullPath);
            var options = new JsonSerializerOptions
            {
                AllowTrailingCommas = true,
                PropertyNameCaseInsensitive = true,
                ReadCommentHandling = JsonCommentHandling.Skip,
                Converters =
                {
                    new JsonStringEnumConverter(
                        namingPolicy: null,
                        allowIntegerValues: false
                    )
                }
            };

            //
            // Serialized Root Object ...
            var root = JsonSerializer.Deserialize<XPromptsDto>(json, options);
            if (!root.IsNullOrDefault())
            {
                prompts = new XPromptsDto { Prompts = root.Prompts };
            }
        }

        /// <summary>
        /// Retrieve all Allowed Prompts as Enumerable ...
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<XPromptDto> GetPrompts()
        {
            return prompts!.Prompts.AsEnumerable();
        }

        /// <summary>
        /// Get Specified Prompt ...
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public static XPromptDto GetPrompt(string name)
        {
            //
            return GetPrompts()
                .FirstOrDefault(p => p.Name == name);
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiServicceBkp\Interfaces\IXAiService.cs

using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.AI;
using xAiService.Configurations;
using xIdentityModels.Models;
using xIdentityService.Interfaces;

namespace xAiService.Interfaces
{
    public interface IXAiService : IXBaseIdentityHttpProvider
    {
        /// <summary>
        /// XAiService Configurations ...
        /// </summary>
        /// <value></value>
        XAIServiceConfiguration Configuration { get; }

        //
        #region Text Actions ...
        /// <summary>
        /// Generated Response ...
        /// </summary>
        /// <param name="prompt"></param>
        /// <param name="userInfo"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public Task<ChatResponse> GetResponseAsync(
            string prompt,
            XUserClaimsInfoDto userInfo,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Generated Response ...
        /// </summary>
        /// <param name="prompt"></param>
        /// <param name="userInfo"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public Task<string> GetTextResponseAsync(
            string prompt,
            XUserClaimsInfoDto userInfo,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Generate Text Response Stream ...
        /// </summary>
        /// <param name="prompt"></param>
        /// <param name="userInfo"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<Stream> GetTextReponseStreamAsync(
            string prompt,
            XUserClaimsInfoDto userInfo,
            CancellationToken cancellationToken = default
        );
        #endregion
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiServicceBkp\Interfaces\IXAiServiceControllerBase.cs

using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

namespace xAiService.Interfaces
{
    public interface IXAiServiceControllerBase
    {
        Task<ActionResult<string>> Ask(
            [FromQuery] string prompt,
            CancellationToken cancellationToken = default
        );
        
        Task<ActionResult> AskStream(
            [FromQuery] string prompt,
            CancellationToken cancellationToken = default
        );        
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiServicceBkp\Models\Dtos\XPromptsDto.cs

namespace xAiService.Models.Dtos
{
    /// <summary>
    /// Described Prompts in app ...
    /// </summary>
    public class XPromptsDto
    {
        public XPromptDto[] Prompts { get; set; } = [];
    }

    /// <summary>
    /// a Single Prompt Description ...
    /// </summary>
    public class XPromptDto
    {
        /// <summary>
        /// Prompt Name ...
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// Prompt Template Message ...
        /// </summary>
        public string Prompt { get; set; }

        /// <summary>
        /// Prompt Description ...
        /// </summary>
        public string Description { get; set; }

        /// <summary>
        /// Describe Prompt Params ...
        /// </summary>
        public virtual XPromptParamDto[] Params { get; set; } = null;
    }

    /// <summary>
    /// Prompt Param used to Fill dynamic Datas to Specified Prompts ...
    /// </summary>
    public class XPromptParamDto
    {
        /// <summary>
        /// Param Name ...
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// Parma Identifier ...
        /// </summary>
        public string Param { get; set; }

        /// <summary>
        /// Param Description ...
        /// </summary>
        public string Description { get; set; }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Documents\Temp\AiServicceBkp\Services\XAiService.cs

using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
using xAiService.Configurations;
using xAiService.Constants;
using xAiService.Interfaces;
using xCommons.Providers;
using xHttpService.Constants;
using xIdentityModels.Models;
using xIdentityService.Interfaces;
using xIdentityService.Providers;

namespace xAiService.Services
{
    public class XAiService : XBaseIdentityHttpProvider, IXAiService
    {
        /// <summary>
        /// XAiService Configurations ...
        /// </summary>
        /// <value></value>
        public XAIServiceConfiguration Configuration { get; }

        public XAiService(
            ILogger<XAiService> logger,
            IXTokenProvider tokenProvider,
            IXIdentityProvider identityProvider,
            XAIServiceConfiguration configuration,
            XValidationProvider validationProvider
        ) : base(
            configuration.Url,
            logger,
            configuration.XPoweredValue,
            configuration.DefaultClientTimeout,
            tokenProvider,
            identityProvider,
            validationProvider
        )
        { }

        //
        #region Text Actions ...
        /// <summary>
        /// Generated Response ...
        /// </summary>
        /// <param name="prompt"></param>
        /// <param name="userInfo"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public Task<ChatResponse> GetResponseAsync(
            string prompt,
            XUserClaimsInfoDto userInfo,
            CancellationToken cancellationToken = default
        )
        {
            throw new System.NotImplementedException();
        }

        /// <summary>
        /// Generated Response ...
        /// </summary>
        /// <param name="prompt"></param>
        /// <param name="userInfo"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<string> GetTextResponseAsync(
            string prompt,
            XUserClaimsInfoDto userInfo,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Validate Args ...
            ValidationProvider.NotEmpty(prompt);

            //
            var client = GetRestClient(userInfo);
            var request = GetRestRequet(
                endpoint: XAiApiEndpoint.Ask,
                addXPoweredValue: true,
                queryStrings: new Dictionary<string, string>
                {
                    { XAiApiQueryParams.Prompt, prompt }
                }
            );

            //
            var response = await RunRestRequest<string>(
                client,
                request,
                XHttpMethod.GET,
                cancellationToken: cancellationToken
            );

            //
            return response;
        }

        /// <summary>
        /// Generate Text Response Stream ...
        /// </summary>
        /// <param name="prompt"></param>
        /// <param name="userInfo"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<Stream> GetTextReponseStreamAsync(
            string prompt,
            XUserClaimsInfoDto userInfo,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Validate Args ...
            ValidationProvider
                .GroupValidationBuilder()
                .AddNotEmpty(prompt)
                .AddNotNull(userInfo)
                .AddNotZero(userInfo.ExpiresAt)
                .AddNotEmpty(userInfo.AccessToken)
                .AddNotEmpty(userInfo.RefreshToken)
                .ValidateGroup();

            //
            var tokens = ToTokenResponse(userInfo);
            var result = await StreamData(
                tokens: tokens,
                addXPoweredValue: true,
                endpoint: XAiApiEndpoint.Stream,
                cancellationToken: cancellationToken,
                queryStrings: new Dictionary<string, string>
                {
                    { XAiApiQueryParams.Prompt, prompt }
                }
            );

            //
            return result;
        }
        #endregion
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Configurations\XAiApiConfiguration.cs

using xAiModels.Models;
using xAiModels.Constants;

namespace xAiModels.Configurations
{
    public class XAiApiConfiguration
    {
        /// <summary>
        /// Model Providers ...
        /// </summary>
        public XAiModelDescriptor[] Models { get; set; } = [];

        /// <summary>
        /// Each User must Has a Default Project, this is Title Resource Identifier ... 
        /// </summary>
        public string DefaultProjectTitleResource { get; set; } = XAiModelsConstants.DefaultXAiProjectTitleResourceID;

        /// <summary>
        /// Each User must Has a Default Project, this is Description Resource Identifier ... 
        /// </summary>
        public string DefaultProjectDescriptionResource { get; set; } = XAiModelsConstants.DefaultXAiProjectDescriptionResourceID;

        /// <summary>
        /// Each User must Has a Default Conversation, this is Title Resource Identifier ...  ...
        /// </summary>
        public string DefaultConversationTitleResource { get; set; } = XAiModelsConstants.DefaultXAiConversationTitleResourceID;
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Configurations\XAiModelMappersProfile.cs

using AutoMapper;
using xAiModels.Models.Dtos;
using xAiModels.Models.Entities;

namespace xAiModels.Configurations
{
    public class XAiModelMappersProfile : Profile
    {
        public XAiModelMappersProfile()
        {
            //
            // XAiMessage ...
            CreateMap<XAiMessage, XAiMessageDto>();

            //
            // XAiProject ...
            CreateMap<XAiProject, XAiProjectDto>();

            //
            // XAiConversation ...
            CreateMap<XAiConversation, XAiConversationDto>();
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Configurations\Entities\XAiConversationEntityConfiguration.cs

using System;
using xAiModels.Constants;
using xDataService.Providers;
using xAiModels.Models.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace xAiModels.Configurations.Entities
{
    public class XAiConversationEntityConfiguration : XBaseEntityTypeConfiguration<XAiConversation, Guid>
    {
        public override void Configure(EntityTypeBuilder<XAiConversation> builder)
        {
            builder.ToTable(XAiModelsConstants.XAiConversationTable);
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Configurations\Entities\XAiMessageEntityConfiguration.cs

using System;
using xAiModels.Constants;
using xDataService.Providers;
using xAiModels.Models.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace xAiModels.Configurations.Entities
{
    public class XAiMessageEntityConfiguration : XBaseEntityTypeConfiguration<XAiMessage, Guid>
    {
        public override void Configure(EntityTypeBuilder<XAiMessage> builder)
        {
            builder.ToTable(XAiModelsConstants.XAiMessageTable);
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Configurations\Entities\XAiProjectEntityConfiguration.cs

using System;
using xAiModels.Constants;
using xDataService.Providers;
using xAiModels.Models.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace xAiModels.Configurations.Entities
{
    public class XAiProjectEntityConfiguration : XBaseEntityTypeConfiguration<XAiProject, Guid>
    {
        public override void Configure(EntityTypeBuilder<XAiProject> builder)
        {
            builder.ToTable(XAiModelsConstants.XAiProjectTable);
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Constants\ConfigurationNodeNames.cs

namespace xAiModels.Constants
{
    public partial struct ConfigurationNodeNames
    {
        public const string AI_API_NODE = "AiApiConfiguration";
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Constants\XAiChatRole.cs

using xExceptions.Attributes;

namespace xAiModels.Constants
{
    public struct XAiChatRoles
    {
        public const string None = "none";
        public const string User = "user";
        public const string Tool = "tool";
        public const string System = "system";
        public const string Assistant = "assistant";
    }

    public enum XAiChatRole
    {
        [StringValue(XAiChatRoles.None)]
        None,
        [StringValue(XAiChatRoles.User)]
        User,
        [StringValue(XAiChatRoles.Tool)]
        Tool,
        [StringValue(XAiChatRoles.System)]
        System,
        [StringValue(XAiChatRoles.Assistant)]
        Assistant,
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Constants\XAiDataProviderConstants.cs

using xExceptions.Attributes;

namespace xAiModels.Constants
{
    public struct XAiDataProviderConstants
    {
        public const string XDefaultAiProjectSuffix = "DefAiPrj";
        public const string XDefaultAiConversationSuffix = "DefAiCnv";
    }

    public enum XAiDataProviderHubActions
    {
        //
        // Project ...
        [StringValue("ProjectCreated")]
        ProjectCreated,

        [StringValue("ProjectUpdated")]
        ProjectUpdated,

        [StringValue("ProjectRemoved")]
        ProjectRemoved,

        //
        // Conversation ...
        [StringValue("ConversationCreated")]
        ConversationCreated,

        [StringValue("ConversationUpdated")]
        ConversationUpdated,

        [StringValue("ConversationRemoved")]
        ConversationRemoved,

        //
        // Message ...
        [StringValue("MessageCreated")]
        MessageCreated,

        [StringValue("MessageUpdated")]
        MessageUpdated,

        [StringValue("MessageRemoved")]
        MessageRemoved,
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Constants\XAiModelProviderType.cs

namespace xAiModels.Constants
{
    /// <summary>
    /// Specifiy Provider Type ...
    /// </summary>
    public enum XAiModelProviderType
    {
        None,
        OpenAI,
        Ollama,
        DeepSeek,
        HuggingFace        
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Constants\XAiModelsConstants.cs

namespace xAiModels.Constants
{
    public struct XAiModelsConstants
    {
        //
        // Service Extensions Log Tag ...
        public const string XAiModelsDILogTag = "XAiModels";
        
        //
        // Table Mapping Identifiers ...
        public const string XAiMessageTable = "AiMessages";
        public const string XAiProjectTable = "AiProjects";
        public const string XAiConversationTable = "AiConversations";

        //
        // GraphQL Collection Mappings ...
        public const string XAiMessageSingleName = "aiMessage";
        public const string XAiProjectSingleName = "aiProject";
        public const string XAiConversationSignleName = "aiConversation";

        public const string XAiMessageCollectionName = "aiMessages";
        public const string XAiProjectCollectionName = "AiProjectsa";
        public const string XAiConversationCollectionName = "aiConversations";

        //
        // Hubs ...
        public const string XAiMessageDtoHub = "aiMessageDto";
        public const string XAiMessageEntityHub = "aiMessageEntity";

        public const string XAiProjectDtoHub = "aiProjectDto";
        public const string XAiProjectEntityHub = "aiProjectEntity";

        public const string XAiConversationDtoHub = "aiConversationDto";
        public const string XAiConversationEntityHub = "aiConversationEntity";

        //
        // Resource Provider Identifiers ...
        public const string XAiProjectTitleIdentifier = "AiPrjT";
        public const string XAiProjectDescriptionIdentifier = "AiPrjD";
        public const string XAiConversationTitleIdentifier = "AiCnvrT";

        //
        // EF Repositories Helper Extensions ...
        public const string GetXAiMessageEFRepositoryDescriptor = "GetXAiMessageEFRepositoryDescriptor";
        public const string GetXAiProjectEFRepositoryDescriptor = "GetXAiProjectEFRepositoryDescriptor";
        public const string GetXAiConversationEFRepositoryDescriptor = "GetXAiConversationEFRepositoryDescriptor";

        //
        public const string DefaultXAiProjectTitleResourceID = "XAiPrjT";         
        public const string DefaultXAiConversationTitleResourceID = "XAiCnvT";         
        public const string DefaultXAiProjectDescriptionResourceID = "XAiPrjD";         

        //
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\DI\XDIHelperExtension.cs

using System;
using xDataService.Db;
using xPushService.DI;
using xDataService.DI;
using xCommons.Helpers;
using xAiModels.Helpers;
using xDataService.Models;
using xAiModels.Providers;
using xAiModels.Constants;
using xCommons.Extensions;
using xPushService.Helpers;
using xAiModels.Interfaces;
using xExceptions.Constants;
using xDataService.Constants;
using xAiModels.Providers.Dtos;
using xAiModels.Interfaces.Dtos;
using Microsoft.AspNetCore.Builder;
using xAiModels.Providers.Entities;
using xAiModels.Interfaces.Entities;
using xAiModels.Providers.Enrichers;
using xAiModels.Interfaces.Enrichers;
using Microsoft.Extensions.DependencyInjection;

namespace xAiModels.DI
{
    /// <summary>
    /// Service Registration Extensions Classes ...
    /// </summary>
    public static class XDIHelperExtension
    {
        /// <summary>
        /// Register Service ...
        /// </summary>
        /// <param name="services"></param>
        /// <param name="repositoryType"></param>
        /// <param name="lifeTime"></param>
        public static void AddXAiDataService(
            this IServiceCollection services,
            XRepositoryType repositoryType,
            ServiceLifetime lifeTime = ServiceLifetime.Scoped
        )
        {
            AddAiDataService(
                contextType: null,
                services: services,
                lifeTime: lifeTime,
                repositoryType: repositoryType
            );
        }

        /// <summary>
        /// Register Service ...
        /// </summary>
        /// <typeparam name="TContext"></typeparam>
        /// <param name="services"></param>
        /// <param name="repositoryType"></param>
        /// <param name="lifeTime"></param>
        public static void AddXAiDataService<TContext>(
            this IServiceCollection services,
            XRepositoryType repositoryType,
            ServiceLifetime lifeTime = ServiceLifetime.Scoped
        )
        where TContext : XDbContext
        {
            AddAiDataService(
                services: services,
                lifeTime: lifeTime,
                contextType: typeof(TContext),
                repositoryType: repositoryType
            );
        }

        /// <summary>
        /// Use XSubCategoryService Middleware ... 
        /// </summary>
        /// <param name="app"></param>
        /// <param name="isDevelopmentEnvironment"></param>
        public static void UseXAiDataService(
            this IApplicationBuilder app,
            bool isDevelopmentEnvironment = false
        )
        {
            //
            #region Using Hubs ...
            //
            var pushHelper = new XPushServiceHelper();

            //
            // XAiMessage ...
            pushHelper.AddHub<XAiMessageDtoHub>(XAiModelsConstants.XAiMessageDtoHub);
            pushHelper.AddHub<XAiMessageEntityHub>(XAiModelsConstants.XAiMessageEntityHub);

            //
            // XAiProject ...
            pushHelper.AddHub<XAiProjectDtoHub>(XAiModelsConstants.XAiProjectDtoHub);
            pushHelper.AddHub<XAiProjectEntityHub>(XAiModelsConstants.XAiProjectEntityHub);

            //
            // XAiConversation ...
            pushHelper.AddHub<XAiConversationDtoHub>(XAiModelsConstants.XAiConversationDtoHub);
            pushHelper.AddHub<XAiConversationEntityHub>(XAiModelsConstants.XAiConversationEntityHub);

            //
            app.UseXPushService(pushHelper);
            #endregion

            //
            // Using XAiConversation Repository Descriptor ...
            if (!aiConversationDescriptor.IsNull())
            {
                //
                app.UseXRepository(
                    descriptor: aiConversationDescriptor,
                    isDevelopmentEnvironment: isDevelopmentEnvironment
                );
            }

            //
            // Using XAiMessage Repository Descriptor ...
            if (!aiMessageDescriptor.IsNull())
            {
                //
                app.UseXRepository(
                    descriptor: aiMessageDescriptor,
                    isDevelopmentEnvironment: isDevelopmentEnvironment
                );
            }

            //
            // Using XAiProject Repository Descriptor ...
            if (!aiProjectDescriptor.IsNull())
            {
                //
                app.UseXRepository(
                    descriptor: aiProjectDescriptor,
                    isDevelopmentEnvironment: isDevelopmentEnvironment
                );
            }

            //
            // Create an Scope for Services Acces ...
            // Handle Seeding Category/SubCategory Resources ...
            // TODO: Enable Seeding if Required ...
            // using (var scope = app.ApplicationServices.CreateScope())
            // {
            //     //
            //     // Retrieve Configuratuions ...
            //     var aiMessageProvider = scope.ServiceProvider.GetService<IXAiMessageServiceProvider>();
            //     var aiProjectProvider = scope.ServiceProvider.GetService<IXAiProjectServiceProvider>();
            //     var aiConversationProvider = scope.ServiceProvider.GetService<IXAiConversationServiceProvider>();
            //     var configuration = scope.ServiceProvider.GetService<XAiDataServiceServiceConfiguration>();
            //     if (!configuration.IsNullOrDefault() &&
            //         !categoryProvider.IsNullOrDefault() &&
            //         !subaiConversationDescriptor.IsNullOrDefault()
            //     )
            //     {
            //         //
            //         // Logging ...
            //         Log("Start Seeding ...");

            //         //
            //         // Count Data in Category ...
            //         var hasCategory = (categoryProvider.Count().GetAwaiter().GetResult()) > 0;
            //         var hasSubCategory = (subCategoryProvider.Count().GetAwaiter().GetResult()) > 0;

            //         //
            //         // Seed Category Resources ...
            //         var canSeed =
            //             !hasCategory &&
            //             configuration.Categories.HasChild();
            //         if (canSeed)
            //         {
            //             //
            //             // Loop through Provided items ...
            //             foreach (var cat in configuration.Categories)
            //             {
            //                 //
            //                 try
            //                 {
            //                     //
            //                     Log($"Start Seeding Category: {cat.Titles.Locales.ToArray()[0].Value} ...");

            //                     //
            //                     categoryProvider.Refresh(
            //                         resource: cat,
            //                         userInfo: null,
            //                         connectionId: null,
            //                         cancellationToken: default
            //                     )
            //                     .GetAwaiter()
            //                     .GetResult();

            //                     //
            //                     Log($"Seeding Category: {cat.Titles.Locales.ToArray()[0].Value} Finished Successfully ...");
            //                 }
            //                 catch (Exception ex)
            //                 {
            //                     Log($"Seeding Category: {cat.Titles.Locales.ToArray()[0].Value} Failed due: {ex.Message} ...");
            //                 }
            //             }
            //         }

            //         //
            //         // Seed SubCategory Resources ...
            //         canSeed =
            //             !hasSubCategory &&
            //             configuration.SubCategories.HasChild();
            //         if (canSeed)
            //         {
            //             //
            //             // Loop through Provided items ...
            //             foreach (var subcat in configuration.SubCategories)
            //             {
            //                 //
            //                 try
            //                 {
            //                     //
            //                     Log($"Start Seeding SubCategory: {subcat.Titles.Locales.ToArray()[0].Value} ...");

            //                     //
            //                     subCategoryProvider.Refresh(
            //                         userInfo: null,
            //                         resource: subcat,
            //                         connectionId: null,
            //                         cancellationToken: default
            //                     )
            //                     .GetAwaiter()
            //                     .GetResult();

            //                     //
            //                     Log($"Seeding SubCategory: {subcat.Titles.Locales.ToArray()[0].Value} Finished Successfully ...");
            //                 }
            //                 catch (Exception ex)
            //                 {
            //                     Log($"Seeding SubCategory: {subcat.Titles.Locales.ToArray()[0].Value} Failed due: {ex.Message} ...");
            //                 }
            //             }
            //         }

            //         //
            //         Log("Finish Seeding ...");
            //     }
            // }
        }

        //
        #region Private ...
        private static XRepositoryDescriptor aiMessageDescriptor = null;
        private static XRepositoryDescriptor aiProjectDescriptor = null;
        private static XRepositoryDescriptor aiConversationDescriptor = null;

        /// <summary>
        /// a LogTag for Service ...
        /// </summary>
        private static string XLogTag = XAiModelsConstants.XAiModelsDILogTag;

        /// <summary>
        /// print a log in Console ...
        /// </summary>
        /// <param name="message"></param>
        private static void Log(string message)
        {
            Console.WriteLine($"{XLogTag} => {message}");
        }

        /// <summary>
        /// Register Service ...
        /// </summary>
        /// <param name="services"></param>
        /// <param name="repositoryType"></param>
        /// <param name="contextType"></param>
        /// <param name="lifeTime"></param>
        private static void AddAiDataService(
            IServiceCollection services,
            XRepositoryType repositoryType,
            Type contextType = null,
            ServiceLifetime lifeTime = ServiceLifetime.Scoped
        )
        {
            //
            var exception = XException.InvalidArgs.ToException(); ;

            //
            // Services ...
            var isServicesExists = !services.IsNull();
            if (!isServicesExists)
            {
                //
                Log("Service Registration failed, services not provided ...");
                throw exception;
            }

            //
            // Lifetime ...
            var isLifetimeExists = !lifeTime.IsNull();
            if (!isLifetimeExists)
            {
                //
                Log("Service Registration failed, lifetime not provided ...");
                throw exception;
            }

            //
            // Validate Repository Type ...
            var isRepositoryTypeValid =
                (repositoryType == XRepositoryType.EF &&
                !contextType.IsNull()) ||
                ((repositoryType == XRepositoryType.Mongo ||
                    repositoryType == XRepositoryType.InMemory) &&
                contextType.IsNull());
            if (!isRepositoryTypeValid)
            {
                //
                Log("Service Registration failed, Invalid Repository Type and Context Type ...");
                throw exception;
            }

            //
            // Maske Instance of Repository Descriptor 
            // Based on Provided Type ...
            switch (repositoryType)
            {
                //
                case XRepositoryType.EF:
                    //
                    // XAiMessage ...
                    aiMessageDescriptor = typeof(XAiModelsHelper)
                        .InvokeGenericMethod<XRepositoryDescriptor>(
                            args: null,
                            runtimeType: contextType,
                            methodName: XAiModelsConstants.GetXAiMessageEFRepositoryDescriptor
                    );

                    //
                    // XAiProject ...
                    aiProjectDescriptor = typeof(XAiModelsHelper)
                        .InvokeGenericMethod<XRepositoryDescriptor>(
                            args: null,
                            runtimeType: contextType,
                            methodName: XAiModelsConstants.GetXAiProjectEFRepositoryDescriptor
                    );

                    //
                    // XAiConversation ...
                    aiConversationDescriptor = typeof(XAiModelsHelper)
                        .InvokeGenericMethod<XRepositoryDescriptor>(
                            args: null,
                            runtimeType: contextType,
                            methodName: XAiModelsConstants.GetXAiConversationEFRepositoryDescriptor
                    );
                    break;

                //
                case XRepositoryType.Mongo:
                    //
                    // XAiMessage ...
                    aiMessageDescriptor = XAiModelsHelper.GetXAiMessageMongoRepositoryDescriptor();

                    //
                    // XAiProject ...
                    aiProjectDescriptor = XAiModelsHelper.GetXAiProjectMongoRepositoryDescriptor();

                    //
                    // XAiConversation ...
                    aiConversationDescriptor = XAiModelsHelper.GetXAiConversationMongoRepositoryDescriptor();
                    break;

                //
                case XRepositoryType.InMemory:
                    //
                    // XAiMessage ...
                    aiMessageDescriptor = XAiModelsHelper.GetXAiMessageInMemoryRepositoryDescriptor();

                    //
                    // XAiProject ...
                    aiProjectDescriptor = XAiModelsHelper.GetXAiProjectInMemoryRepositoryDescriptor();

                    //
                    // XAiConversation ...
                    aiConversationDescriptor = XAiModelsHelper.GetXAiConversationInMemoryRepositoryDescriptor();
                    break;
            }

            //
            #region Register Repository Descriptor ...
            //
            // XAiMessage ...
            if (!aiMessageDescriptor.IsNull())
            {
                //
                services.AddXRepository(
                    lifeTime: lifeTime,
                    descriptor: aiMessageDescriptor
                );
            }

            //
            // XAiProject ...
            if (!aiProjectDescriptor.IsNull())
            {
                //
                services.AddXRepository(
                    lifeTime: lifeTime,
                    descriptor: aiProjectDescriptor
                );
            }

            //
            // XAiConversation ...
            if (!aiConversationDescriptor.IsNull())
            {
                //
                services.AddXRepository(
                    lifeTime: lifeTime,
                    descriptor: aiConversationDescriptor
                );
            }
            #endregion

            //
            #region Register Services ...
            //
            #region AiMessage ...
            //
            services.Add(
                new ServiceDescriptor(typeof(IXAiMessageServiceProvider), typeof(XAiMessageServiceProvider), lifeTime)
            );
            services.Add(
                new ServiceDescriptor(typeof(IXAiMessageRepositoryService), typeof(XAiMessageRepositoryService), lifeTime)
            );
            services.Add(
                new ServiceDescriptor(typeof(IXAiMessageRepositoryProvider), typeof(XAiMessageRepositoryProvider), lifeTime)
            );

            //
            // Enrichers ...
            services.Add(
                new ServiceDescriptor(typeof(IXAiMessageEnricher), typeof(XAiMessageEnricher), lifeTime)
            );
            #endregion

            //
            #region AiProject ...
            //
            services.Add(
                new ServiceDescriptor(typeof(IXAiProjectServiceProvider), typeof(XAiProjectServiceProvider), lifeTime)
            );
            services.Add(
                new ServiceDescriptor(typeof(IXAiProjectRepositoryService), typeof(XAiProjectRepositoryService), lifeTime)
            );
            services.Add(
                new ServiceDescriptor(typeof(IXAiProjectRepositoryProvider), typeof(XAiProjectRepositoryProvider), lifeTime)
            );

            //
            // Resource Providers ...
            services.Add(
                new ServiceDescriptor(typeof(IXAiProjectTitleResourceProvider), typeof(XAiProjectTitleResourceProvider), lifeTime)
            );
            services.Add(
                new ServiceDescriptor(typeof(IXAiProjectDescriptionResourceProvider), typeof(XAiProjectDescriptionResourceProvider), lifeTime)
            );

            //
            // Enrichers ...
            services.Add(
                new ServiceDescriptor(typeof(IXAiProjectEnricher), typeof(XAiProjectEnricher), lifeTime)
            );
            #endregion

            //
            #region AiConversation ...
            //
            services.Add(
                new ServiceDescriptor(typeof(IXAiConversationServiceProvider), typeof(XAiConversationServiceProvider), lifeTime)
            );
            services.Add(
                new ServiceDescriptor(typeof(IXAiConversationRepositoryService), typeof(XAiConversationRepositoryService), lifeTime)
            );
            services.Add(
                new ServiceDescriptor(typeof(IXAiConversationRepositoryProvider), typeof(XAiConversationRepositoryProvider), lifeTime)
            );

            //
            // Resource Providers ...
            services.Add(
                new ServiceDescriptor(typeof(IXAiConversationTitleResourceProvider), typeof(XAiConversationTitleResourceProvider), lifeTime)
            );

            //
            // Enrichers ...
            services.Add(
                new ServiceDescriptor(typeof(IXAiConversationEnricher), typeof(XAiConversationEnricher), lifeTime)
            );
            #endregion
            #endregion

            //
            // Data Provider ...
            services.Add(
                new ServiceDescriptor(typeof(IXAiDataProvider), typeof(XAiDataProvider), lifeTime)
            );

            //
            Log("Service Regitration Succeed ...");
        }
        #endregion        
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Extensions\ChatResponseExtensions.cs

using xCommons.Extensions;
using Microsoft.Extensions.AI;

namespace xAiModels.Extensions
{
    public static class ChatResponseExtensions
    {
        /// <summary>
        /// Validate a Chat Response ...
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsValid(this ChatResponse source)
        {
            //
            var result =
                source is not null &&
                !source.IsNullOrDefault() &&
                !string.IsNullOrWhiteSpace(source.Text) &&
                source.FinishReason == ChatFinishReason.Stop;

            //
            return result;
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Extensions\ChatRoleExtensions.cs

using xAiModels.Constants;
using xCommons.Extensions;
using Microsoft.Extensions.AI;

namespace xAiModels.Extensions
{
    /// <summary>
    /// Extensions for ChatRole ...
    /// </summary>
    public static class ChatRoleExtensions
    {
        /// <summary>
        /// Normalize an String to Propper Chat Role Value ...
        /// <see cref="XAiChatRoles"/>>
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static string GetChatRoleTitle(this string source)
        {
            //
            var result = XAiChatRoles.None;

            //
            if (source.ToNormalString() == XAiChatRoles.None.ToNormalString())
            {
                result = XAiChatRoles.None;
            }
            else if (source.ToNormalString() == XAiChatRoles.User.ToNormalString())
            {
                result = XAiChatRoles.User;
            }
            else if (source.ToNormalString() == XAiChatRoles.Tool.ToNormalString())
            {
                result = XAiChatRoles.Tool;
            }
            else if (source.ToNormalString() == XAiChatRoles.System.ToNormalString())
            {
                result = XAiChatRoles.System;
            }
            else if (source.ToNormalString() == XAiChatRoles.Assistant.ToNormalString())
            {
                result = XAiChatRoles.Assistant;
            }

            //
            return result;
        }

        /// <summary>
        /// Get Chat Role Title from Chat Role Class ...
        /// <see cref="XAiChatRoles"/>
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static string GetRole(this ChatRole source)
        {
            //
            var result = source.Value
                .GetChatRoleTitle();

            //
            return result;
        }

        /// <summary>
        /// Generate Chat Role Class Based on role Title ...
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static ChatRole ToChatRole(this string source)
        {
            //
            var role = source.GetChatRoleTitle();
            ChatRole result = new ChatRole(role);

            //
            return result;
        }

        /// <summary>
        /// Generate Chat Role Class Based on role Title ...
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static ChatRole ToChatRole(this XAiChatRole source)
        {
            //
            var role = source.GetStringValue();
            ChatRole result = new ChatRole(role);

            //
            return result;
        }

        /// <summary>
        /// Extract Chat Role Enum from Chat Role ...
        /// <see cref="ChatRole"/>
        /// <see cref="XAiChatRole"/>
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static XAiChatRole ToChatRole(this ChatRole source)
        {
            //
            var role = source
                .GetRole()
                .GetChatRoleTitle();

            //
            XAiChatRole result = XAiChatRole.User;

            //
            if (role == XAiChatRoles.User)
            {
                result = XAiChatRole.User;
            }
            else if (role == XAiChatRoles.Tool)
            {
                result = XAiChatRole.Tool;
            }
            else if (role == XAiChatRoles.System)
            {
                result = XAiChatRole.System;
            }
            else if (role == XAiChatRoles.Assistant)
            {
                result = XAiChatRole.Assistant;
            }

            //
            return result;
        }

        /// <summary>
        /// Validate an String as Chat Role ...
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsValid(this string source)
        {
            //
            var result =
                source != XAiChatRoles.None &&
                source.ToNormalString() != XAiChatRoles.None.ToNormalString();

            //
            return result;
        }

        /// <summary>
        /// Validate Role ...
        /// <see cref="XAiChatRole"/>>
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsValid(this XAiChatRole source)
        {
            //
            var result = source != XAiChatRole.None;
            return result;
        }

        /// <summary>
        /// Validate Role ...
        /// <see cref="ChatRole"/>>
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsValid(this ChatRole source)
        {
            //
            var role = source.ToChatRole();
            var result = role.IsValid();
            return result;
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Extensions\XAiApiConfigurationExtensions.cs

using System.Linq;
using xAiModels.Models;
using xCommons.Extensions;
using xAiModels.Configurations;

namespace xAiModels.Extensions
{
    public static class XAiApiConfigurationExtensions
    {
        /// <summary>
        /// Validate Api Configuration ...
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsValid(this XAiApiConfiguration source)
        {
            return
                !source.IsNullOrDefault() &&
                source.Models.HasChild();
        }

        /// <summary>
        /// Retrieve Default Model Descriptor ...
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static XAiModelDescriptor GetDefaultModel(this XAiApiConfiguration source)
        {
            //
            XAiModelDescriptor result = null;

            //
            if (source.IsValid())
            {
                result = source.Models.First();
            }

            //
            return result;
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Extensions\XAiModelsExtensions.cs

using System.Linq;
using xAiModels.Models;
using xCommons.Extensions;
using xAiModels.Models.Dtos;
using xAiModels.Models.Entities;

namespace xAiModels.Extensions
{
    /// <summary>
    /// Extensions Methods for XAiModels ...
    /// </summary>
    public static class XAiModelsExtensions
    {
        /// <summary>
        /// Validate a Project for Add Or Update ...
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsValidForAddOrUpdate(this XAiProject source)
        {
            //
            var result =
                !source.IsNullOrDefault() &&
                !source.OwnerId.IsNullOrEmpty() &&
                !source.Title.IsNullOrEmpty() &&
                !source.Prompt.IsNullOrEmpty();

            //
            return result;
        }
        public static bool IsValidForAddOrUpdate(this XAiProjectDto source)
        {
            //
            var result =
                !source.IsNullOrDefault() &&
                !source.OwnerId.IsNullOrEmpty() &&
                !source.Title.IsNullOrEmpty() &&
                !source.Prompt.IsNullOrEmpty();

            //
            return result;
        }

        /// <summary>
        /// Validate a Conversation for Add Or Update ...
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsValidForAddOrUpdate(this XAiConversation source)
        {
            //
            var result =
                !source.IsNullOrDefault() &&
                !source.OwnerId.IsNullOrEmpty() &&
                !source.Title.IsNullOrEmpty() &&
                !source.ProjectId.IsNull() &&
                !source.ProjectId.IsDefaultGuid();

            //
            return result;
        }
        public static bool IsValidForAddOrUpdate(this XAiConversationDto source)
        {
            //
            var result =
                !source.IsNullOrDefault() &&
                !source.OwnerId.IsNullOrEmpty() &&
                !source.Title.IsNullOrEmpty() &&
                !source.ProjectId.IsNull() &&
                !source.ProjectId.IsDefaultGuid();

            //
            return result;
        }

        /// <summary>
        /// Validate a Message for Add Or Update ...
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsValidForAddOrUpdate(this XAiMessage source)
        {
            //
            var result =
                !source.IsNullOrDefault() &&
                !source.OwnerId.IsNullOrEmpty() &&
                source.Role.IsValid() &&
                !source.ConversationId.IsNull() &&
                !source.ConversationId.IsDefaultGuid() &&
                !source.Content.IsNullOrEmpty();

            //
            return result;
        }
        public static bool IsValidForAddOrUpdate(this XAiMessageDto source)
        {
            //
            var result =
                !source.IsNullOrDefault() &&
                !source.OwnerId.IsNullOrEmpty() &&
                source.Role.IsValid() &&
                !source.ConversationId.IsNull() &&
                !source.ConversationId.IsDefaultGuid() &&
                !source.Content.IsNullOrEmpty();

            //
            return result;
        }

        /// <summary>
        /// Validate a Model Descriptor ...
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsValid(this XAiModelDescriptor source)
        {
            //
            var result =
                !source.IsNullOrDefault() &&
                !source.Name.IsNullOrEmpty() &&
                !source.Url.IsNullOrEmpty() &&
                source.Url.IsValidUrl() &&
                !source.LLM.IsNullOrEmpty() &&
                source.Provider != Constants.XAiModelProviderType.None;

            //
            return result;
        }

        /// <summary>
        /// Validate Specified Param ...
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsValid(this XAiPromptParam source)
        {
            //
            var result =
                !source.IsNullOrDefault() &&
                !source.Name.IsNullOrEmpty() &&
                !source.Param.IsNullOrEmpty();

            //
            return result;
        }

        /// <summary>
        /// Validate Specified Prompt ...
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static bool IsValid(this XAiPromptDescriptor source)
        {
            //
            var result =
                !source.IsNullOrDefault() &&
                !source.Name.IsNullOrEmpty() &&
                !source.Template.IsNullOrEmpty();
            if (!result)
            {
                return result;
            }

            //
            if (source.Params.HasChild())
            {
                result =
                    result &&
                    source.Params.All(x => x.IsValid());
            }

            //
            return result;
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Helpers\XAiModelsHelper.cs

using System;
using GraphQL.Types;
using xDataService.Db;
using xDataService.Models;
using xDataService.Providers;
using xAiModels.Models.Entities;
using xAiModels.Providers.GraphQL;
using xAiModels.Providers.Entities;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Helpers
{
    public static class XAiModelsHelper
    {
        //
        #region XAiConversation ...
        //
        #region EF Repository Descriptor Provider(s) ...
        public static XEFRepositoryDescriptor<
            XAiConversation,
            Guid,
            IXAiConversationKeyGenerator,
            XAiConversationKeyGenerator,
            IXAiConversationRepositoryEvents,
            XAiConversationRepositoryEvents,
            XAiConversationGraphType,
            GuidGraphType,
            XAiConversationGraphQuery,
            IXAiConversationGraphQLTypeHelper,
            XAiConversationGraphQLTypeHelper,
            XAiConversationGraphSchema,
            IXAiConversationRepository,
            XAiConversationEFRepository<TContext>,
            TContext
        > GetXAiConversationEFRepositoryDescriptor<TContext>()
        where TContext : XDbContext
        {
            //
            var result = new XEFRepositoryDescriptor<
                XAiConversation,
                Guid,
                IXAiConversationKeyGenerator,
                XAiConversationKeyGenerator,
                IXAiConversationRepositoryEvents,
                XAiConversationRepositoryEvents,
                XAiConversationGraphType,
                GuidGraphType,
                XAiConversationGraphQuery,
                IXAiConversationGraphQLTypeHelper,
                XAiConversationGraphQLTypeHelper,
                XAiConversationGraphSchema,
                IXAiConversationRepository,
                XAiConversationEFRepository<TContext>,
                TContext
            >();

            //
            return result;
        }

        public static XEFRepositoryDescriptor<
            XAiConversation,
            Guid,
            IXAiConversationKeyGenerator,
            XAiConversationKeyGenerator,
            IXAiConversationRepositoryEvents,
            XAiConversationRepositoryEvents,
            XAiConversationGraphType,
            GuidGraphType,
            XAiConversationGraphQuery,
            IXAiConversationGraphQLTypeHelper,
            XAiConversationGraphQLTypeHelper,
            XAiConversationGraphSchema,
            IXAiConversationRepository,
            XAiConversationEFRepository<TContext>,
            TContext,
            IXAiConversationSeeder,
            TSeederImplementation
        > GetXAiConversationEFRepositoryDescriptor<TContext, TSeederImplementation>()
        where TContext : XDbContext
        where TSeederImplementation : XBaseDbSeeder<XAiConversation, Guid>
        {
            //
            var result = new XEFRepositoryDescriptor<
                XAiConversation,
                Guid,
                IXAiConversationKeyGenerator,
                XAiConversationKeyGenerator,
                IXAiConversationRepositoryEvents,
                XAiConversationRepositoryEvents,
                XAiConversationGraphType,
                GuidGraphType,
                XAiConversationGraphQuery,
                IXAiConversationGraphQLTypeHelper,
                XAiConversationGraphQLTypeHelper,
                XAiConversationGraphSchema,
                IXAiConversationRepository,
                XAiConversationEFRepository<TContext>,
                TContext,
                IXAiConversationSeeder,
                TSeederImplementation
            >();

            //
            return result;
        }
        #endregion

        //
        #region Mongo Repository Descriptor Provider(s) ...
        public static XMongoRepositoryDescriptor<
            XAiConversation,
            Guid,
            IXAiConversationKeyGenerator,
            XAiConversationKeyGenerator,
            IXAiConversationRepositoryEvents,
            XAiConversationRepositoryEvents,
            XAiConversationGraphType,
            GuidGraphType,
            XAiConversationGraphQuery,
            IXAiConversationGraphQLTypeHelper,
            XAiConversationGraphQLTypeHelper,
            XAiConversationGraphSchema,
            IXAiConversationRepository,
            XAiConversationMongoRepository
        > GetXAiConversationMongoRepositoryDescriptor()
        {
            //
            var result = new XMongoRepositoryDescriptor<
                XAiConversation,
                Guid,
                IXAiConversationKeyGenerator,
                XAiConversationKeyGenerator,
                IXAiConversationRepositoryEvents,
                XAiConversationRepositoryEvents,
                XAiConversationGraphType,
                GuidGraphType,
                XAiConversationGraphQuery,
                IXAiConversationGraphQLTypeHelper,
                XAiConversationGraphQLTypeHelper,
                XAiConversationGraphSchema,
                IXAiConversationRepository,
                XAiConversationMongoRepository
            >();

            //
            return result;
        }

        public static XMongoRepositoryDescriptor<
            XAiConversation,
            Guid,
            IXAiConversationKeyGenerator,
            XAiConversationKeyGenerator,
            IXAiConversationRepositoryEvents,
            XAiConversationRepositoryEvents,
            XAiConversationGraphType,
            GuidGraphType,
            XAiConversationGraphQuery,
            IXAiConversationGraphQLTypeHelper,
            XAiConversationGraphQLTypeHelper,
            XAiConversationGraphSchema,
            IXAiConversationRepository,
            XAiConversationMongoRepository,
            IXAiConversationSeeder,
            TSeederImplementation
        > GetXAiConversationMongoRepositoryDescriptor<TSeederImplementation>()
        where TSeederImplementation : XBaseDbSeeder<XAiConversation, Guid>
        {
            //
            var result = new XMongoRepositoryDescriptor<
                XAiConversation,
                Guid,
                IXAiConversationKeyGenerator,
                XAiConversationKeyGenerator,
                IXAiConversationRepositoryEvents,
                XAiConversationRepositoryEvents,
                XAiConversationGraphType,
                GuidGraphType,
                XAiConversationGraphQuery,
                IXAiConversationGraphQLTypeHelper,
                XAiConversationGraphQLTypeHelper,
                XAiConversationGraphSchema,
                IXAiConversationRepository,
                XAiConversationMongoRepository,
                IXAiConversationSeeder,
                TSeederImplementation
            >();

            //
            return result;
        }
        #endregion

        //
        #region InMemory Repository Descriptor Provider(s) ...
        public static XInMemoryRepositoryDescriptor<
            XAiConversation,
            Guid,
            IXAiConversationKeyGenerator,
            XAiConversationKeyGenerator,
            IXAiConversationRepositoryEvents,
            XAiConversationRepositoryEvents,
            XAiConversationGraphType,
            GuidGraphType,
            XAiConversationGraphQuery,
            IXAiConversationGraphQLTypeHelper,
            XAiConversationGraphQLTypeHelper,
            XAiConversationGraphSchema,
            IXAiConversationRepository,
            XAiConversationInMemoryRepository
        > GetXAiConversationInMemoryRepositoryDescriptor()
        {
            //
            var result = new XInMemoryRepositoryDescriptor<
                XAiConversation,
                Guid,
                IXAiConversationKeyGenerator,
                XAiConversationKeyGenerator,
                IXAiConversationRepositoryEvents,
                XAiConversationRepositoryEvents,
                XAiConversationGraphType,
                GuidGraphType,
                XAiConversationGraphQuery,
                IXAiConversationGraphQLTypeHelper,
                XAiConversationGraphQLTypeHelper,
                XAiConversationGraphSchema,
                IXAiConversationRepository,
                XAiConversationInMemoryRepository
            >();

            //
            return result;
        }

        public static XInMemoryRepositoryDescriptor<
            XAiConversation,
            Guid,
            IXAiConversationKeyGenerator,
            XAiConversationKeyGenerator,
            IXAiConversationRepositoryEvents,
            XAiConversationRepositoryEvents,
            XAiConversationGraphType,
            GuidGraphType,
            XAiConversationGraphQuery,
            IXAiConversationGraphQLTypeHelper,
            XAiConversationGraphQLTypeHelper,
            XAiConversationGraphSchema,
            IXAiConversationRepository,
            XAiConversationInMemoryRepository,
            IXAiConversationSeeder,
            TSeederImplementation
        > GetXAiConversationInMemoryRepositoryDescriptor<TSeederImplementation>()
        where TSeederImplementation : XBaseDbSeeder<XAiConversation, Guid>
        {
            //
            var result = new XInMemoryRepositoryDescriptor<
                XAiConversation,
                Guid,
                IXAiConversationKeyGenerator,
                XAiConversationKeyGenerator,
                IXAiConversationRepositoryEvents,
                XAiConversationRepositoryEvents,
                XAiConversationGraphType,
                GuidGraphType,
                XAiConversationGraphQuery,
                IXAiConversationGraphQLTypeHelper,
                XAiConversationGraphQLTypeHelper,
                XAiConversationGraphSchema,
                IXAiConversationRepository,
                XAiConversationInMemoryRepository,
                IXAiConversationSeeder,
                TSeederImplementation
            >();

            //
            return result;
        }
        #endregion        
        #endregion

        //
        #region XAiMessage ...
        //
        #region EF Repository Descriptor Provider(s) ...
        public static XEFRepositoryDescriptor<
            XAiMessage,
            Guid,
            IXAiMessageKeyGenerator,
            XAiMessageKeyGenerator,
            IXAiMessageRepositoryEvents,
            XAiMessageRepositoryEvents,
            XAiMessageGraphType,
            GuidGraphType,
            XAiMessageGraphQuery,
            IXAiMessageGraphQLTypeHelper,
            XAiMessageGraphQLTypeHelper,
            XAiMessageGraphSchema,
            IXAiMessageRepository,
            XAiMessageEFRepository<TContext>,
            TContext
        > GetXAiMessageEFRepositoryDescriptor<TContext>()
        where TContext : XDbContext
        {
            //
            var result = new XEFRepositoryDescriptor<
                XAiMessage,
                Guid,
                IXAiMessageKeyGenerator,
                XAiMessageKeyGenerator,
                IXAiMessageRepositoryEvents,
                XAiMessageRepositoryEvents,
                XAiMessageGraphType,
                GuidGraphType,
                XAiMessageGraphQuery,
                IXAiMessageGraphQLTypeHelper,
                XAiMessageGraphQLTypeHelper,
                XAiMessageGraphSchema,
                IXAiMessageRepository,
                XAiMessageEFRepository<TContext>,
                TContext
            >();

            //
            return result;
        }

        public static XEFRepositoryDescriptor<
            XAiMessage,
            Guid,
            IXAiMessageKeyGenerator,
            XAiMessageKeyGenerator,
            IXAiMessageRepositoryEvents,
            XAiMessageRepositoryEvents,
            XAiMessageGraphType,
            GuidGraphType,
            XAiMessageGraphQuery,
            IXAiMessageGraphQLTypeHelper,
            XAiMessageGraphQLTypeHelper,
            XAiMessageGraphSchema,
            IXAiMessageRepository,
            XAiMessageEFRepository<TContext>,
            TContext,
            IXAiMessageSeeder,
            TSeederImplementation
        > GetXAiMessageEFRepositoryDescriptor<TContext, TSeederImplementation>()
        where TContext : XDbContext
        where TSeederImplementation : XBaseDbSeeder<XAiMessage, Guid>
        {
            //
            var result = new XEFRepositoryDescriptor<
                XAiMessage,
                Guid,
                IXAiMessageKeyGenerator,
                XAiMessageKeyGenerator,
                IXAiMessageRepositoryEvents,
                XAiMessageRepositoryEvents,
                XAiMessageGraphType,
                GuidGraphType,
                XAiMessageGraphQuery,
                IXAiMessageGraphQLTypeHelper,
                XAiMessageGraphQLTypeHelper,
                XAiMessageGraphSchema,
                IXAiMessageRepository,
                XAiMessageEFRepository<TContext>,
                TContext,
                IXAiMessageSeeder,
                TSeederImplementation
            >();

            //
            return result;
        }
        #endregion

        //
        #region Mongo Repository Descriptor Provider(s) ...
        public static XMongoRepositoryDescriptor<
            XAiMessage,
            Guid,
            IXAiMessageKeyGenerator,
            XAiMessageKeyGenerator,
            IXAiMessageRepositoryEvents,
            XAiMessageRepositoryEvents,
            XAiMessageGraphType,
            GuidGraphType,
            XAiMessageGraphQuery,
            IXAiMessageGraphQLTypeHelper,
            XAiMessageGraphQLTypeHelper,
            XAiMessageGraphSchema,
            IXAiMessageRepository,
            XAiMessageMongoRepository
        > GetXAiMessageMongoRepositoryDescriptor()
        {
            //
            var result = new XMongoRepositoryDescriptor<
                XAiMessage,
                Guid,
                IXAiMessageKeyGenerator,
                XAiMessageKeyGenerator,
                IXAiMessageRepositoryEvents,
                XAiMessageRepositoryEvents,
                XAiMessageGraphType,
                GuidGraphType,
                XAiMessageGraphQuery,
                IXAiMessageGraphQLTypeHelper,
                XAiMessageGraphQLTypeHelper,
                XAiMessageGraphSchema,
                IXAiMessageRepository,
                XAiMessageMongoRepository
            >();

            //
            return result;
        }

        public static XMongoRepositoryDescriptor<
            XAiMessage,
            Guid,
            IXAiMessageKeyGenerator,
            XAiMessageKeyGenerator,
            IXAiMessageRepositoryEvents,
            XAiMessageRepositoryEvents,
            XAiMessageGraphType,
            GuidGraphType,
            XAiMessageGraphQuery,
            IXAiMessageGraphQLTypeHelper,
            XAiMessageGraphQLTypeHelper,
            XAiMessageGraphSchema,
            IXAiMessageRepository,
            XAiMessageMongoRepository,
            IXAiMessageSeeder,
            TSeederImplementation
        > GetXAiMessageMongoRepositoryDescriptor<TSeederImplementation>()
        where TSeederImplementation : XBaseDbSeeder<XAiMessage, Guid>
        {
            //
            var result = new XMongoRepositoryDescriptor<
                XAiMessage,
                Guid,
                IXAiMessageKeyGenerator,
                XAiMessageKeyGenerator,
                IXAiMessageRepositoryEvents,
                XAiMessageRepositoryEvents,
                XAiMessageGraphType,
                GuidGraphType,
                XAiMessageGraphQuery,
                IXAiMessageGraphQLTypeHelper,
                XAiMessageGraphQLTypeHelper,
                XAiMessageGraphSchema,
                IXAiMessageRepository,
                XAiMessageMongoRepository,
                IXAiMessageSeeder,
                TSeederImplementation
            >();

            //
            return result;
        }
        #endregion

        //
        #region InMemory Repository Descriptor Provider(s) ...
        public static XInMemoryRepositoryDescriptor<
            XAiMessage,
            Guid,
            IXAiMessageKeyGenerator,
            XAiMessageKeyGenerator,
            IXAiMessageRepositoryEvents,
            XAiMessageRepositoryEvents,
            XAiMessageGraphType,
            GuidGraphType,
            XAiMessageGraphQuery,
            IXAiMessageGraphQLTypeHelper,
            XAiMessageGraphQLTypeHelper,
            XAiMessageGraphSchema,
            IXAiMessageRepository,
            XAiMessageInMemoryRepository
        > GetXAiMessageInMemoryRepositoryDescriptor()
        {
            //
            var result = new XInMemoryRepositoryDescriptor<
                XAiMessage,
                Guid,
                IXAiMessageKeyGenerator,
                XAiMessageKeyGenerator,
                IXAiMessageRepositoryEvents,
                XAiMessageRepositoryEvents,
                XAiMessageGraphType,
                GuidGraphType,
                XAiMessageGraphQuery,
                IXAiMessageGraphQLTypeHelper,
                XAiMessageGraphQLTypeHelper,
                XAiMessageGraphSchema,
                IXAiMessageRepository,
                XAiMessageInMemoryRepository
            >();

            //
            return result;
        }

        public static XInMemoryRepositoryDescriptor<
            XAiMessage,
            Guid,
            IXAiMessageKeyGenerator,
            XAiMessageKeyGenerator,
            IXAiMessageRepositoryEvents,
            XAiMessageRepositoryEvents,
            XAiMessageGraphType,
            GuidGraphType,
            XAiMessageGraphQuery,
            IXAiMessageGraphQLTypeHelper,
            XAiMessageGraphQLTypeHelper,
            XAiMessageGraphSchema,
            IXAiMessageRepository,
            XAiMessageInMemoryRepository,
            IXAiMessageSeeder,
            TSeederImplementation
        > GetXAiMessageInMemoryRepositoryDescriptor<TSeederImplementation>()
        where TSeederImplementation : XBaseDbSeeder<XAiMessage, Guid>
        {
            //
            var result = new XInMemoryRepositoryDescriptor<
                XAiMessage,
                Guid,
                IXAiMessageKeyGenerator,
                XAiMessageKeyGenerator,
                IXAiMessageRepositoryEvents,
                XAiMessageRepositoryEvents,
                XAiMessageGraphType,
                GuidGraphType,
                XAiMessageGraphQuery,
                IXAiMessageGraphQLTypeHelper,
                XAiMessageGraphQLTypeHelper,
                XAiMessageGraphSchema,
                IXAiMessageRepository,
                XAiMessageInMemoryRepository,
                IXAiMessageSeeder,
                TSeederImplementation
            >();

            //
            return result;
        }
        #endregion
        #endregion

        //
        #region XAiProject ...
        //
        #region EF Repository Descriptor Provider(s) ...
        public static XEFRepositoryDescriptor<
            XAiProject,
            Guid,
            IXAiProjectKeyGenerator,
            XAiProjectKeyGenerator,
            IXAiProjectRepositoryEvents,
            XAiProjectRepositoryEvents,
            XAiProjectGraphType,
            GuidGraphType,
            XAiProjectGraphQuery,
            IXAiProjectGraphQLTypeHelper,
            XAiProjectGraphQLTypeHelper,
            XAiProjectGraphSchema,
            IXAiProjectRepository,
            XAiProjectEFRepository<TContext>,
            TContext
        > GetXAiProjectEFRepositoryDescriptor<TContext>()
        where TContext : XDbContext
        {
            //
            var result = new XEFRepositoryDescriptor<
                XAiProject,
                Guid,
                IXAiProjectKeyGenerator,
                XAiProjectKeyGenerator,
                IXAiProjectRepositoryEvents,
                XAiProjectRepositoryEvents,
                XAiProjectGraphType,
                GuidGraphType,
                XAiProjectGraphQuery,
                IXAiProjectGraphQLTypeHelper,
                XAiProjectGraphQLTypeHelper,
                XAiProjectGraphSchema,
                IXAiProjectRepository,
                XAiProjectEFRepository<TContext>,
                TContext
            >();

            //
            return result;
        }

        public static XEFRepositoryDescriptor<
            XAiProject,
            Guid,
            IXAiProjectKeyGenerator,
            XAiProjectKeyGenerator,
            IXAiProjectRepositoryEvents,
            XAiProjectRepositoryEvents,
            XAiProjectGraphType,
            GuidGraphType,
            XAiProjectGraphQuery,
            IXAiProjectGraphQLTypeHelper,
            XAiProjectGraphQLTypeHelper,
            XAiProjectGraphSchema,
            IXAiProjectRepository,
            XAiProjectEFRepository<TContext>,
            TContext,
            IXAiProjectSeeder,
            TSeederImplementation
        > GetXAiProjectEFRepositoryDescriptor<TContext, TSeederImplementation>()
        where TContext : XDbContext
        where TSeederImplementation : XBaseDbSeeder<XAiProject, Guid>
        {
            //
            var result = new XEFRepositoryDescriptor<
                XAiProject,
                Guid,
                IXAiProjectKeyGenerator,
                XAiProjectKeyGenerator,
                IXAiProjectRepositoryEvents,
                XAiProjectRepositoryEvents,
                XAiProjectGraphType,
                GuidGraphType,
                XAiProjectGraphQuery,
                IXAiProjectGraphQLTypeHelper,
                XAiProjectGraphQLTypeHelper,
                XAiProjectGraphSchema,
                IXAiProjectRepository,
                XAiProjectEFRepository<TContext>,
                TContext,
                IXAiProjectSeeder,
                TSeederImplementation
            >();

            //
            return result;
        }
        #endregion

        //
        #region Mongo Repository Descriptor Provider(s) ...
        public static XMongoRepositoryDescriptor<
            XAiProject,
            Guid,
            IXAiProjectKeyGenerator,
            XAiProjectKeyGenerator,
            IXAiProjectRepositoryEvents,
            XAiProjectRepositoryEvents,
            XAiProjectGraphType,
            GuidGraphType,
            XAiProjectGraphQuery,
            IXAiProjectGraphQLTypeHelper,
            XAiProjectGraphQLTypeHelper,
            XAiProjectGraphSchema,
            IXAiProjectRepository,
            XAiProjectMongoRepository
        > GetXAiProjectMongoRepositoryDescriptor()
        {
            //
            var result = new XMongoRepositoryDescriptor<
                XAiProject,
                Guid,
                IXAiProjectKeyGenerator,
                XAiProjectKeyGenerator,
                IXAiProjectRepositoryEvents,
                XAiProjectRepositoryEvents,
                XAiProjectGraphType,
                GuidGraphType,
                XAiProjectGraphQuery,
                IXAiProjectGraphQLTypeHelper,
                XAiProjectGraphQLTypeHelper,
                XAiProjectGraphSchema,
                IXAiProjectRepository,
                XAiProjectMongoRepository
            >();

            //
            return result;
        }

        public static XMongoRepositoryDescriptor<
            XAiProject,
            Guid,
            IXAiProjectKeyGenerator,
            XAiProjectKeyGenerator,
            IXAiProjectRepositoryEvents,
            XAiProjectRepositoryEvents,
            XAiProjectGraphType,
            GuidGraphType,
            XAiProjectGraphQuery,
            IXAiProjectGraphQLTypeHelper,
            XAiProjectGraphQLTypeHelper,
            XAiProjectGraphSchema,
            IXAiProjectRepository,
            XAiProjectMongoRepository,
            IXAiProjectSeeder,
            TSeederImplementation
        > GetXAiProjectMongoRepositoryDescriptor<TSeederImplementation>()
        where TSeederImplementation : XBaseDbSeeder<XAiProject, Guid>
        {
            //
            var result = new XMongoRepositoryDescriptor<
                XAiProject,
                Guid,
                IXAiProjectKeyGenerator,
                XAiProjectKeyGenerator,
                IXAiProjectRepositoryEvents,
                XAiProjectRepositoryEvents,
                XAiProjectGraphType,
                GuidGraphType,
                XAiProjectGraphQuery,
                IXAiProjectGraphQLTypeHelper,
                XAiProjectGraphQLTypeHelper,
                XAiProjectGraphSchema,
                IXAiProjectRepository,
                XAiProjectMongoRepository,
                IXAiProjectSeeder,
                TSeederImplementation
            >();

            //
            return result;
        }
        #endregion

        //
        #region InMemory Repository Descriptor Provider(s) ...
        public static XInMemoryRepositoryDescriptor<
            XAiProject,
            Guid,
            IXAiProjectKeyGenerator,
            XAiProjectKeyGenerator,
            IXAiProjectRepositoryEvents,
            XAiProjectRepositoryEvents,
            XAiProjectGraphType,
            GuidGraphType,
            XAiProjectGraphQuery,
            IXAiProjectGraphQLTypeHelper,
            XAiProjectGraphQLTypeHelper,
            XAiProjectGraphSchema,
            IXAiProjectRepository,
            XAiProjectInMemoryRepository
        > GetXAiProjectInMemoryRepositoryDescriptor()
        {
            //
            var result = new XInMemoryRepositoryDescriptor<
                XAiProject,
                Guid,
                IXAiProjectKeyGenerator,
                XAiProjectKeyGenerator,
                IXAiProjectRepositoryEvents,
                XAiProjectRepositoryEvents,
                XAiProjectGraphType,
                GuidGraphType,
                XAiProjectGraphQuery,
                IXAiProjectGraphQLTypeHelper,
                XAiProjectGraphQLTypeHelper,
                XAiProjectGraphSchema,
                IXAiProjectRepository,
                XAiProjectInMemoryRepository
            >();

            //
            return result;
        }

        public static XInMemoryRepositoryDescriptor<
            XAiProject,
            Guid,
            IXAiProjectKeyGenerator,
            XAiProjectKeyGenerator,
            IXAiProjectRepositoryEvents,
            XAiProjectRepositoryEvents,
            XAiProjectGraphType,
            GuidGraphType,
            XAiProjectGraphQuery,
            IXAiProjectGraphQLTypeHelper,
            XAiProjectGraphQLTypeHelper,
            XAiProjectGraphSchema,
            IXAiProjectRepository,
            XAiProjectInMemoryRepository,
            IXAiProjectSeeder,
            TSeederImplementation
        > GetXAiProjectInMemoryRepositoryDescriptor<TSeederImplementation>()
        where TSeederImplementation : XBaseDbSeeder<XAiProject, Guid>
        {
            //
            var result = new XInMemoryRepositoryDescriptor<
                XAiProject,
                Guid,
                IXAiProjectKeyGenerator,
                XAiProjectKeyGenerator,
                IXAiProjectRepositoryEvents,
                XAiProjectRepositoryEvents,
                XAiProjectGraphType,
                GuidGraphType,
                XAiProjectGraphQuery,
                IXAiProjectGraphQLTypeHelper,
                XAiProjectGraphQLTypeHelper,
                XAiProjectGraphSchema,
                IXAiProjectRepository,
                XAiProjectInMemoryRepository,
                IXAiProjectSeeder,
                TSeederImplementation
            >();

            //
            return result;
        }
        #endregion        
        #endregion    
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\IXAiConversationTitleResourceProvider.cs

using xStringService.Interfaces;

namespace xAiModels.Interfaces
{
    public interface IXAiConversationTitleResourceProvider : IXOwnedItemResourceProvider
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\IXAiDataProvider.cs

using System;
using System.Threading;
using xAiModels.Models.Dtos;
using System.Threading.Tasks;
using System.Collections.Generic;

namespace xAiModels.Interfaces
{
    /// <summary>
    /// All Data Manipulation Requirements ...
    /// </summary>
    public interface IXAiDataProvider
    {
        //
        #region Loaders ...
        /// <summary>
        /// Load Projects List of Specified Owner ...
        /// </summary>
        /// <param name="ownerId"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<IEnumerable<XAiProjectDto>> LoadProjects(
            string ownerId,
            bool fillConversations = false,
            bool fillConversationMessages = false,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Load List of Conversations for Specified Project ... 
        /// </summary>
        /// <param name="projectId"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<IEnumerable<XAiConversationDto>> LoadConversations(
            Guid projectId,
            bool fillMessages = false,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Load Messages of Specified Conversation ...
        /// </summary>
        /// <param name="conversationId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<IEnumerable<XAiMessageDto>> LoadMessages(
            Guid conversationId,
            CancellationToken cancellationToken = default
        );
        #endregion

        //
        #region Creator/Adders ...
        /// <summary>
        /// Create Project ...
        /// </summary>
        /// <param name="ownerId"></param>
        /// <param name="item"></param>
        /// <param name="connectionId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XAiProjectDto> CreateProject(
            string ownerId,
            XAiProjectDto item = null,
            string connectionId = null,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Create Conversation ...
        /// </summary>
        /// <param name="projectId"></param>
        /// <param name="item"></param>
        /// <param name="connectionId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XAiConversationDto> CreateConversation(
            Guid projectId,
            XAiConversationDto item = null,
            string connectionId = null,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Add Message ...
        /// </summary>
        /// <param name="conversationId"></param>
        /// <param name="item"></param>
        /// <param name="connectionId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XAiMessageDto> AddMessage(
            Guid conversationId,
            XAiMessageDto item,
            string connectionId = null,
            CancellationToken cancellationToken = default
        );
        #endregion    
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\IXAiProjectDescriptionResourceProvider.cs

using xStringService.Interfaces;

namespace xAiModels.Interfaces
{
    public interface IXAiProjectDescriptionResourceProvider : IXOwnedItemResourceProvider
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\IXAiProjectTitleResourceProvider.cs

using xStringService.Interfaces;

namespace xAiModels.Interfaces
{
    public interface IXAiProjectTitleResourceProvider : IXOwnedItemResourceProvider
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Dtos\IXAiConversationDtoHub.cs

using System;
using xAiModels.Models.Dtos;
using xPushService.Interfaces;
using xAiModels.Models.Entities;

namespace xAiModels.Interfaces.Dtos
{
    public interface IXAiConversationDtoHub : IXBaseDtoHub<XAiConversation, XAiConversationDto, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Dtos\IXAiConversationRepositoryService.cs

using System;
using System.Threading;
using xAiModels.Models.Dtos;
using System.Threading.Tasks;
using xDataService.Interfaces;
using xAiModels.Models.Entities;
using System.Collections.Generic;

namespace xAiModels.Interfaces.Dtos
{
    public interface IXAiConversationRepositoryService : IXBaseRepositoryService<XAiConversation, XAiConversationDto, Guid>
    {
        //
        #region Enrichers ...
        /// <summary>
        /// Enrich Specified Item ...
        /// </summary>
        /// <param name="source"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XAiConversationDto> Enrich(
            XAiConversation source,
            bool fillMessages = false,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Enrich Specified Item ...
        /// </summary>
        /// <param name="source"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XAiConversationDto> Enrich(
            XAiConversationDto source,
            bool fillMessages = false,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Enrich Specified Items ...
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<IEnumerable<XAiConversationDto>> EnrichRange(
            IEnumerable<XAiConversation> sources,
            bool fillMessages = false,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Enrich Specified Items ...
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<IEnumerable<XAiConversationDto>> EnrichRange(
            IEnumerable<XAiConversationDto> sources,
            bool fillMessages = false,
            CancellationToken cancellationToken = default
        );
        #endregion                
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Dtos\IXAiConversationServiceProvider.cs

using System;
using xAiModels.Models.Dtos;
using xPushService.Interfaces;
using xAiModels.Providers.Dtos;
using xAiModels.Models.Entities;

namespace xAiModels.Interfaces.Dtos
{
    public interface IXAiConversationServiceProvider : IXBaseDtoProvider<XAiConversation, XAiConversationDto, Guid, XAiConversationDtoHub>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Dtos\IXAiMessageDtoHub.cs

using System;
using xAiModels.Models.Dtos;
using xPushService.Interfaces;
using xAiModels.Models.Entities;

namespace xAiModels.Interfaces.Dtos
{
    public interface IXAiMessageDtoHub : IXBaseDtoHub<XAiMessage, XAiMessageDto, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Dtos\IXAiMessageRepositoryService.cs

using System;
using System.Threading;
using xAiModels.Models.Dtos;
using System.Threading.Tasks;
using xDataService.Interfaces;
using xAiModels.Models.Entities;
using System.Collections.Generic;

namespace xAiModels.Interfaces.Dtos
{
    public interface IXAiMessageRepositoryService : IXBaseRepositoryService<XAiMessage, XAiMessageDto, Guid>
    {
        //
        #region Enrichers ...
        /// <summary>
        /// Enrich Specified Item ...
        /// </summary>
        /// <param name="source"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XAiMessageDto> Enrich(
            XAiMessage source,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Enrich Specified Item ...
        /// </summary>
        /// <param name="source"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XAiMessageDto> Enrich(
            XAiMessageDto source,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Enrich Specified Items ...
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<IEnumerable<XAiMessageDto>> EnrichRange(
            IEnumerable<XAiMessage> sources,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Enrich Specified Items ...
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<IEnumerable<XAiMessageDto>> EnrichRange(
            IEnumerable<XAiMessageDto> sources,
            CancellationToken cancellationToken = default
        );
        #endregion
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Dtos\IXAiMessageServiceProvider.cs

using System;
using xAiModels.Models.Dtos;
using xPushService.Interfaces;
using xAiModels.Providers.Dtos;
using xAiModels.Models.Entities;

namespace xAiModels.Interfaces.Dtos
{
    public interface IXAiMessageServiceProvider : IXBaseDtoProvider<XAiMessage, XAiMessageDto, Guid, XAiMessageDtoHub>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Dtos\IXAiProjectDtoHub.cs

using System;
using xAiModels.Models.Dtos;
using xPushService.Interfaces;
using xAiModels.Models.Entities;

namespace xAiModels.Interfaces.Dtos
{
    public interface IXAiProjectDtoHub : IXBaseDtoHub<XAiProject, XAiProjectDto, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Dtos\IXAiProjectRepositoryService.cs

using System;
using System.Threading;
using xAiModels.Models.Dtos;
using System.Threading.Tasks;
using xDataService.Interfaces;
using xAiModels.Models.Entities;
using System.Collections.Generic;

namespace xAiModels.Interfaces.Dtos
{
    public interface IXAiProjectRepositoryService : IXBaseRepositoryService<XAiProject, XAiProjectDto, Guid>
    {
        //
        #region Enrichers ...
        /// <summary>
        /// Enrich Specified Item ...
        /// </summary>
        /// <param name="source"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XAiProjectDto> Enrich(
            XAiProject source,
            bool fillConversations = false,
            bool fillConversationMessages = false,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Enrich Specified Item ...
        /// </summary>
        /// <param name="source"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XAiProjectDto> Enrich(
            XAiProjectDto source,
            bool fillConversations = false,
            bool fillConversationMessages = false,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Enrich Specified Items ...
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<IEnumerable<XAiProjectDto>> EnrichRange(
            IEnumerable<XAiProject> sources,
            bool fillConversations = false,
            bool fillConversationMessages = false,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Enrich Specified Items ...
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<IEnumerable<XAiProjectDto>> EnrichRange(
            IEnumerable<XAiProjectDto> sources,
            bool fillConversations = false,
            bool fillConversationMessages = false,
            CancellationToken cancellationToken = default
        );
        #endregion        
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Dtos\IXAiProjectServiceProvider.cs

using System;
using xAiModels.Models.Dtos;
using xPushService.Interfaces;
using xAiModels.Providers.Dtos;
using xAiModels.Models.Entities;

namespace xAiModels.Interfaces.Dtos
{
    public interface IXAiProjectServiceProvider : IXBaseDtoProvider<XAiProject, XAiProjectDto, Guid, XAiProjectDtoHub>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Enrichers\IXAiConversationEnricher.cs

using System.Threading;
using xAiModels.Models.Dtos;
using System.Threading.Tasks;
using xAiModels.Models.Entities;
using System.Collections.Generic;

namespace xAiModels.Interfaces.Enrichers
{
    /// <summary>
    /// Ai Conversation Enricher ...
    /// </summary>
    public interface IXAiConversationEnricher
    {
        //
        #region Enrichers ...
        /// <summary>
        /// Enrich Specified Item ...
        /// </summary>
        /// <param name="source"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XAiConversationDto> Enrich(
            XAiConversation source,
            bool fillMessages = false,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Enrich Specified Item ...
        /// </summary>
        /// <param name="source"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XAiConversationDto> Enrich(
            XAiConversationDto source,
            bool fillMessages = false,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Enrich Specified Items ...
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<IEnumerable<XAiConversationDto>> EnrichRange(
            IEnumerable<XAiConversation> sources,
            bool fillMessages = false,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Enrich Specified Items ...
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<IEnumerable<XAiConversationDto>> EnrichRange(
            IEnumerable<XAiConversationDto> sources,
            bool fillMessages = false,
            CancellationToken cancellationToken = default
        );
        #endregion                
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Enrichers\IXAiMessageEnricher.cs

using System.Threading;
using xAiModels.Models.Dtos;
using System.Threading.Tasks;
using xAiModels.Models.Entities;
using System.Collections.Generic;

namespace xAiModels.Interfaces.Enrichers
{
    public interface IXAiMessageEnricher
    {
        //
        #region Enrichers ...
        /// <summary>
        /// Enrich Specified Item ...
        /// </summary>
        /// <param name="source"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XAiMessageDto> Enrich(
            XAiMessage source,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Enrich Specified Item ...
        /// </summary>
        /// <param name="source"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XAiMessageDto> Enrich(
            XAiMessageDto source,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Enrich Specified Items ...
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<IEnumerable<XAiMessageDto>> EnrichRange(
            IEnumerable<XAiMessage> sources,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Enrich Specified Items ...
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<IEnumerable<XAiMessageDto>> EnrichRange(
            IEnumerable<XAiMessageDto> sources,
            CancellationToken cancellationToken = default
        );
        #endregion        
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Enrichers\IXAiProjectEnricher.cs

using System.Threading;
using xAiModels.Models.Dtos;
using System.Threading.Tasks;
using xAiModels.Models.Entities;
using System.Collections.Generic;

namespace xAiModels.Interfaces.Enrichers
{
    public interface IXAiProjectEnricher
    {
        //
        #region Enrichers ...
        /// <summary>
        /// Enrich Specified Item ...
        /// </summary>
        /// <param name="source"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XAiProjectDto> Enrich(
            XAiProject source,
            bool fillConversations = false,
            bool fillConversationMessages = false,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Enrich Specified Item ...
        /// </summary>
        /// <param name="source"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<XAiProjectDto> Enrich(
            XAiProjectDto source,
            bool fillConversations = false,
            bool fillConversationMessages = false,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Enrich Specified Items ...
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<IEnumerable<XAiProjectDto>> EnrichRange(
            IEnumerable<XAiProject> sources,
            bool fillConversations = false,
            bool fillConversationMessages = false,
            CancellationToken cancellationToken = default
        );

        /// <summary>
        /// Enrich Specified Items ...
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        Task<IEnumerable<XAiProjectDto>> EnrichRange(
            IEnumerable<XAiProjectDto> sources,
            bool fillConversations = false,
            bool fillConversationMessages = false,
            CancellationToken cancellationToken = default
        );
        #endregion        
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Entities\IXAiConversationEntityHub.cs

using System;
using xPushService.Interfaces;
using xAiModels.Models.Entities;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiConversationEntityHub : IXBaseEntityHub<XAiConversation, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Entities\IXAiConversationGraphQLTypeHelper.cs

using System;
using xDataService.Interfaces;
using xAiModels.Models.Entities;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiConversationGraphQLTypeHelper : IXBaseGraphQLTypeHelper<XAiConversation, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Entities\IXAiConversationKeyGenerator.cs

using System;
using xDataService.Interfaces;
using xAiModels.Models.Entities;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiConversationKeyGenerator : IXKeyGenerator<XAiConversation, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Entities\IXAiConversationRepository.cs

using System;
using xDataService.Interfaces;
using xAiModels.Models.Entities;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiConversationRepository : IXBaseRepository<XAiConversation, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Entities\IXAiConversationRepositoryEvents.cs

using xDataService.Interfaces;
using xAiModels.Models.Entities;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiConversationRepositoryEvents : IXBaseRepositoryEvents<XAiConversation>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Entities\IXAiConversationRepositoryProvider.cs

using System;
using xPushService.Interfaces;
using xAiModels.Models.Entities;
using xAiModels.Providers.Entities;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiConversationRepositoryProvider : IXBaseEntityProvider<XAiConversation, Guid, XAiConversationEntityHub>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Entities\IXAiConversationSeeder.cs

using System;
using xDataService.Interfaces;
using xAiModels.Models.Entities;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiConversationSeeder : IXBaseDbSeeder<XAiConversation, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Entities\IXAiDocumentEntityHub.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiDocumentEntityHub
    {
        
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Entities\IXAiMessageEntityHub.cs

using System;
using xPushService.Interfaces;
using xAiModels.Models.Entities;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiMessageEntityHub : IXBaseEntityHub<XAiMessage, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Entities\IXAiMessageGraphQLTypeHelper.cs

using System;
using xDataService.Interfaces;
using xAiModels.Models.Entities;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiMessageGraphQLTypeHelper : IXBaseGraphQLTypeHelper<XAiMessage, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Entities\IXAiMessageKeyGenerator.cs

using System;
using xDataService.Interfaces;
using xAiModels.Models.Entities;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiMessageKeyGenerator : IXKeyGenerator<XAiMessage, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Entities\IXAiMessageRepository.cs

using System;
using xDataService.Interfaces;
using xAiModels.Models.Entities;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiMessageRepository : IXBaseRepository<XAiMessage, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Entities\IXAiMessageRepositoryEvents.cs

using xDataService.Interfaces;
using xAiModels.Models.Entities;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiMessageRepositoryEvents : IXBaseRepositoryEvents<XAiMessage>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Entities\IXAiMessageRepositoryProvider.cs

using System;
using xPushService.Interfaces;
using xAiModels.Models.Entities;
using xAiModels.Providers.Entities;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiMessageRepositoryProvider : IXBaseEntityProvider<XAiMessage, Guid, XAiMessageEntityHub>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Entities\IXAiMessageSeeder.cs

using System;
using xDataService.Interfaces;
using xAiModels.Models.Entities;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiMessageSeeder : IXBaseDbSeeder<XAiMessage, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Entities\IXAiProjectEntityHub.cs

using System;
using xPushService.Interfaces;
using xAiModels.Models.Entities;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiProjectEntityHub : IXBaseEntityHub<XAiProject, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Entities\IXAiProjectGraphQLTypeHelper.cs

using System;
using xDataService.Interfaces;
using xAiModels.Models.Entities;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiProjectGraphQLTypeHelper : IXBaseGraphQLTypeHelper<XAiProject, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Entities\IXAiProjectKeyGenerator.cs

using System;
using xDataService.Interfaces;
using xAiModels.Models.Entities;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiProjectKeyGenerator : IXKeyGenerator<XAiProject, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Entities\IXAiProjectRepository.cs

using System;
using xDataService.Interfaces;
using xAiModels.Models.Entities;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiProjectRepository : IXBaseRepository<XAiProject, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Entities\IXAiProjectRepositoryEvents.cs

using xDataService.Interfaces;
using xAiModels.Models.Entities;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiProjectRepositoryEvents : IXBaseRepositoryEvents<XAiProject>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Entities\IXAiProjectRepositoryProvider.cs

using System;
using xPushService.Interfaces;
using xAiModels.Models.Entities;
using xAiModels.Providers.Entities;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiProjectRepositoryProvider : IXBaseEntityProvider<XAiProject, Guid, XAiProjectEntityHub>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Interfaces\Entities\IXAiProjectSeeder.cs

using System;
using xDataService.Interfaces;
using xAiModels.Models.Entities;

namespace xAiModels.Interfaces.Entities
{
    public interface IXAiProjectSeeder : IXBaseDbSeeder<XAiProject, Guid>
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Models\XAiModelDescriptor.cs

using xAiModels.Constants;

namespace xAiModels.Models
{
    /// <summary>
    /// Describe a Provided Model ...
    /// </summary>
    public class XAiModelDescriptor
    {
        /// <summary>
        /// Provider Name ...
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// LLM Provider Url ...
        /// </summary>
        public string Url { get; set; }

        /// <summary>
        /// Model Name ...
        /// </summary>
        public string LLM { get; set; }

        /// <summary>
        /// Api Key for Connection ...
        /// </summary>
        public string ApiKey { get; set; }

        /// <summary>
        /// Specified Model Provider Type ...
        /// </summary>
        public XAiModelProviderType Provider { get; set; } = XAiModelProviderType.Ollama;        
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Models\XAiPromptDescriptor.cs

namespace xAiModels.Models
{
    /// <summary>
    /// Describe Specified Prompt ...
    /// </summary>
    public class XAiPromptDescriptor
    {
        /// <summary>
        /// Prompt Name ...
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// Prompt Template ... 
        /// </summary>
        public string Template { get; set; }

        /// <summary>
        /// Prompt Description ...
        /// </summary>
        public string Description { get; set; }

        /// <summary>
        /// Prompt Template Params ...
        /// </summary>
        public XAiPromptParam[] Params { get; set; } = [];
    }

    /// <summary>
    /// Specified a Prompt Param ...
    /// </summary>
    public class XAiPromptParam
    {
        /// <summary>
        /// Param Name ...
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// Param Identifier ...
        /// </summary>
        public string Param { get; set; }

        /// <summary>
        /// Parma Description ...
        /// </summary>
        public string Description { get; set; }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Models\Dtos\XAiConversationDto.cs

using System;
using xModels.Base;
using xModels.Dtos;
using System.Collections.Generic;

namespace xAiModels.Models.Dtos
{
    /// <summary>
    /// Conversation Describe ...
    /// </summary>
    public class XAiConversationDto : XBaseGuidIDEntityDto
    {
        /// <summary>
        /// User Identifier ...
        /// </summary>
        /// <value></value>
        public string OwnerId { get; set; }

        /// <summary>
        /// Owner Info ...
        /// </summary>
        public virtual XPersonDto Owner { get; set; } = null;

        /// <summary>
        /// Conversation Title Resource ID ...
        /// </summary>
        public string Title { get; set; }

        /// <summary>
        /// All Exists Title Locale Resources ...
        /// </summary>
        public virtual XResourceDto TitleResource { get; set; }

        /// <summary>
        /// Created Time ...
        /// </summary>
        public DateTime CreatedOn { get; set; }

        /// <summary>
        /// Updated Time ...
        /// </summary>
        public DateTime UpdatedAt { get; set; }

        /// <summary>
        /// Conversation Messages ...
        /// </summary>
        public virtual IEnumerable<XAiMessageDto> Messages { get; set; } = [];

        /// <summary>
        /// Project ID ...
        /// </summary>
        public Guid ProjectId { get; set; }

        /// <summary>
        /// Project Title Resource ID ...
        /// </summary>
        public virtual string ProjectTitle { get; set; } = null;

        /// <summary>
        /// Project Description Resource ID ...
        /// </summary>
        public virtual string ProjectDescription { get; set; } = null;

        /// <summary>
        /// Project Title Locale Resources ...
        /// </summary>
        public virtual XResourceDto ProjectTitleResource { get; set; }

        /// <summary>
        /// Project Description Locale Resources ...
        /// </summary>
        public virtual XResourceDto ProjectDescriptionResource { get; set; }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Models\Dtos\XAiMessageDto.cs

using System;
using xModels.Base;
using xModels.Dtos;
using xAiModels.Constants;
using xCommons.Extensions;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;

namespace xAiModels.Models.Dtos
{
    public class XAiMessageDto : XBaseGuidIDEntityDto
    {
        /// <summary>
        /// User Identifier ...
        /// </summary>
        /// <value></value>
        public string OwnerId { get; set; }

        /// <summary>
        /// Owner Info ...
        /// </summary>
        public virtual XPersonDto Owner { get; set; } = null;

        /// <summary>
        /// Message of Chat ...
        /// </summary>
        public string Content { get; set; }

        /// <summary>
        /// Role of Message ...
        /// </summary>
        public XAiChatRole Role { get; set; }

        /// <summary>
        /// Created Time ...
        /// </summary>
        public DateTime CreatedOn { get; set; }

        /// <summary>
        /// Updated Time ...
        /// </summary>
        public DateTime UpdatedAt { get; set; }

        /// <summary>
        /// Conversation Id ...
        /// </summary>
        public Guid ConversationId { get; set; }

        /// <summary>
        /// Conversation Info ...
        /// </summary>
        public virtual string ConversationTitle { get; set; } = null;
        public virtual XResourceDto ConversationTitleResouce { get; set; } = null;

        //
        #region Meta Data Property ...
        private string _metadata;

        /// <summary>
        /// Meta Data of Message ...
        /// </summary>
        [NotMapped]
        public IDictionary<string, object> Metadata { get; private set; } = null;

        /// <summary>
        /// Meta Data of MEssage in Json ... 
        /// </summary>
        public string MetaDatas
        {
            //
            get => _metadata;

            //
            set
            {
                //
                // Converts Json String to Dictionary ...
                if (!value.IsNullOrEmpty())
                {
                    Metadata = value.FromJSON<IDictionary<string, object>>();
                }

                //
                _metadata = value;
            }
        }
        #endregion                
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Models\Dtos\XAiProjectDto.cs

using System;
using xModels.Base;
using xModels.Dtos;
using System.Collections.Generic;

namespace xAiModels.Models.Dtos
{
    /// <summary>
    /// Describe a Project ...
    /// </summary>
    public class XAiProjectDto : XBaseGuidIDEntityDto
    {
        /// <summary>
        /// User Identifier ...
        /// </summary>
        /// <value></value>
        public string OwnerId { get; set; }

        /// <summary>
        /// Owner Info ...
        /// </summary>
        public virtual XPersonDto Owner { get; set; } = null;

        /// <summary>
        /// Title of Project ...
        /// </summary>
        public string Title { get; set; }

        /// <summary>
        /// All Exists Title Locale Resources ...
        /// </summary>
        public virtual XResourceDto TitleResource { get; set; }

        /// <summary>
        /// Description of Project ...
        /// </summary>
        public string Description { get; set; }

        /// <summary>
        /// All Exists Description Locale Resources ...
        /// </summary>
        public virtual XResourceDto DescriptionResource { get; set; }

        /// <summary>
        /// a Prompt for Project Start ...
        /// </summary>
        public string Prompt { get; set; }

        /// <summary>
        /// Created Time ...
        /// </summary>
        public DateTime CreatedOn { get; set; }

        /// <summary>
        /// Updated Time ...
        /// </summary>
        public DateTime UpdatedAt { get; set; }

        /// <summary>
        /// Conversations ...
        /// </summary>
        public virtual IList<XAiConversationDto> Conversations { get; set; } = [];
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Models\Entities\XAiConversation.cs

using System;
using xModels.Base;
using System.ComponentModel.DataAnnotations;

namespace xAiModels.Models.Entities
{
    /// <summary>
    /// Conversation Describe ...
    /// </summary>
    public class XAiConversation : XBaseGuidIDEntity
    {
        /// <summary>
        /// User Identifier ...
        /// </summary>
        /// <value></value>
        [Required]
        [StringLength(255)]
        public string OwnerId { get; set; }

        /// <summary>
        /// Title of Conversation ...
        /// </summary>
        [Required]
        [StringLength(255)]
        public string Title { get; set; }

        /// <summary>
        /// Created Time ...
        /// </summary>
        public DateTime CreatedOn { get; set; }

        /// <summary>
        /// Updated Time ...
        /// </summary>
        public DateTime UpdatedAt { get; set; }

        /// <summary>
        /// Project ID ...
        /// </summary>
        [Required]
        public Guid ProjectId { get; set; }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Models\Entities\XAiDocument.cs

using System;
using System.ComponentModel.DataAnnotations;
using xModels.Base;

namespace xAiModels.Models.Entities
{
    /// <summary>
    /// Represents an embedd document in the AI system.
    /// </summary>
    public class XAiDocument : XBaseGuidIDEntity
    {
        /// <summary>
        /// User Identifier ...
        /// </summary>
        /// <value></value>
        [Required]
        [StringLength(255)]
        public string OwnerId { get; set; }

        /// <summary>
        /// Title of Document ...
        /// </summary>
        [StringLength(255)]
        public string Title { get; set; }

        /// <summary>
        /// Content of Document ...
        /// </summary>
        public string Content { get; set; }

        /// <summary>
        /// Hash of the Document Content ...
        /// </summary>
        public string ContentHash { get; set; } = string.Empty;

        /// <summary>
        /// Embedding Model Identifier ...
        /// </summary>
        public string EmbeddingModel { get; set; } = string.Empty;

        /// <summary>
        /// Dimensions of the Embedding ...
        /// </summary>
        public int Dimensions { get; set; }

        /// <summary>
        /// Vector representation of the Document ...
        /// </summary>
        public byte[] Vector { get; set; } = [];

        /// <summary>
        /// Created Time ...
        /// </summary>
        public DateTime CreatedOn { get; set; }

        /// <summary>
        /// Updated Time ...
        /// </summary>
        public DateTime UpdatedAt { get; set; }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Models\Entities\XAiMessage.cs

using System;
using xModels.Base;
using xAiModels.Constants;
using xCommons.Extensions;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace xAiModels.Models.Entities
{
    /// <summary>
    /// Message Model of Ai ... 
    /// </summary>
    public class XAiMessage : XBaseGuidIDEntity
    {
        /// <summary>
        /// User Identifier ...
        /// </summary>
        /// <value></value>
        [Required]
        [StringLength(255)]
        public string OwnerId { get; set; }

        /// <summary>
        /// Message of Chat ...
        /// </summary>
        [Required]
        public string Content { get; set; }

        /// <summary>
        /// Role of Message ...
        /// </summary>
        public XAiChatRole Role { get; set; }

        /// <summary>
        /// Created Time ...
        /// </summary>
        public DateTime CreatedOn { get; set; }

        /// <summary>
        /// Updated Time ...
        /// </summary>
        public DateTime UpdatedAt { get; set; }

        /// <summary>
        /// Conversation Id ...
        /// </summary>
        [StringLength(255)]
        public Guid ConversationId { get; set; }

        //
        #region Meta Data Property ...
        private string _metadata;

        /// <summary>
        /// Meta Data of Message ...
        /// </summary>
        [NotMapped]
        public IDictionary<string, object> Metadata { get; private set; } = null;

        /// <summary>
        /// Meta Data of MEssage in Json ... 
        /// </summary>
        public string MetaDatas
        {
            //
            get => _metadata;

            //
            set
            {
                //
                // Converts Json String to Dictionary ...
                if (!value.IsNullOrEmpty())
                {
                    Metadata = value.FromJSON<IDictionary<string, object>>();
                }

                //
                _metadata = value;
            }
        }
        #endregion        
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Models\Entities\XAiProject.cs

using System;
using xModels.Base;
using System.ComponentModel.DataAnnotations;

namespace xAiModels.Models.Entities
{
    /// <summary>
    /// Describe a Project ...
    /// </summary>
    public class XAiProject : XBaseGuidIDEntity
    {
        /// <summary>
        /// User Identifier ...
        /// </summary>
        /// <value></value>
        [Required]
        [StringLength(255)]
        public string OwnerId { get; set; }

        /// <summary>
        /// Title of Project ...
        /// </summary>
        [Required]
        [StringLength(255)]
        public string Title { get; set; }

        /// <summary>
        /// Description of Project ...
        /// </summary>
        [StringLength(1000)]
        public string Description { get; set; }

        /// <summary>
        /// a Prompt for Project Start ...
        /// </summary>
        public string Prompt { get; set; }

        /// <summary>
        /// Created Time ...
        /// </summary>
        public DateTime CreatedOn { get; set; }

        /// <summary>
        /// Updated Time ...
        /// </summary>
        public DateTime UpdatedAt { get; set; }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\XAiConversationEntityRegisterar.cs

using System;
using xDataService.Providers;
using xDataService.Interfaces;
using xAiModels.Models.Entities;
using Microsoft.EntityFrameworkCore;
using xAiModels.Configurations.Entities;

namespace xAiModels.Providers
{
    public class XAiConversationEntityRegisterar : XBaseEntityRegisterar, IXEntityRegisterar
    {
        public XAiConversationEntityRegisterar()
        : base(nameof(XAiConversationEntityRegisterar))
        {
            //
            // Add Entity ...
            AddEntity<XAiConversation, Guid>();
        }

        /// <summary>
        /// Configure Entities ...
        /// </summary>
        /// <param name="modelBuilder"></param>
        public override void ConfigureEntities(ModelBuilder modelBuilder)
        {
            //
            // Configure Entity Using Provided ...
            modelBuilder
                .ApplyConfigurationsFromAssembly(typeof(XAiConversationEntityConfiguration).Assembly);

            //
            base.ConfigureEntities(modelBuilder);
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\XAiConversationTitleResourceProvider.cs

using xAiModels.Constants;
using xAiModels.Interfaces;
using xCommons.Configurations;
using xStringService.Providers;
using xStringService.Interfaces.Dtos;

namespace xAiModels.Providers
{
    public class XAiConversationTitleResourceProvider : XBaseOwnedItemResourceProvider, IXAiConversationTitleResourceProvider
    {
        public XAiConversationTitleResourceProvider(
            XAppConfiguration appConfig,
            IXStringServiceProvider stringProvider
        ) : base(
            appConfig:  appConfig,
            stringProvider: stringProvider,
            item: XAiModelsConstants.XAiConversationTitleIdentifier
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\XAiDataProvider.cs

using System;
using System.Linq;
using xModels.Dtos;
using System.Threading;
using xAiModels.Constants;
using xCommons.Extensions;
using xAiModels.Extensions;
using xAiModels.Interfaces;
using xAiModels.Models.Dtos;
using xExceptions.Constants;
using System.Threading.Tasks;
using xCommons.Configurations;
using xAiModels.Providers.Hubs;
using xAiModels.Interfaces.Dtos;
using xStringService.Interfaces;
using System.Collections.Generic;
using Microsoft.AspNetCore.SignalR;

namespace xAiModels.Providers
{
    /// <summary>
    /// All Data Manipulation Requirements ...
    /// </summary>
    public class XAiDataProvider : IXAiDataProvider
    {
        //
        #region Properties ...
        private readonly IXStringProvider stringProvider;
        private readonly XAppConfiguration appConfiguration;
        private readonly IHubContext<XAiDataProviderHub> hub;
        private readonly IXAiMessageRepositoryService messageRepositoryService;
        private readonly IXAiProjectRepositoryService projectRepositoryService;
        private readonly IXAiProjectTitleResourceProvider projectTitleResourceProvider;
        private readonly IXAiConversationRepositoryService conversationRepositoryService;
        private readonly IXAiConversationTitleResourceProvider conversationTitleResourceProvider;
        private readonly IXAiProjectDescriptionResourceProvider projectDescriptionResourceProvider;
        #endregion

        public XAiDataProvider(
            IXStringProvider stringProvider,
            XAppConfiguration appConfiguration,
            IHubContext<XAiDataProviderHub> hub,
            IXAiMessageRepositoryService messageRepositoryService,
            IXAiProjectRepositoryService projectRepositoryService,
            IXAiProjectTitleResourceProvider projectTitleResourceProvider,
            IXAiConversationRepositoryService conversationRepositoryService,
            IXAiConversationTitleResourceProvider conversationTitleResourceProvider,
            IXAiProjectDescriptionResourceProvider projectDescriptionResourceProvider
        )
        {
            this.hub = hub;
            this.stringProvider = stringProvider;
            this.appConfiguration = appConfiguration;
            this.messageRepositoryService = messageRepositoryService;
            this.projectRepositoryService = projectRepositoryService;
            this.projectTitleResourceProvider = projectTitleResourceProvider;
            this.conversationRepositoryService = conversationRepositoryService;
            this.conversationTitleResourceProvider = conversationTitleResourceProvider;
            this.projectDescriptionResourceProvider = projectDescriptionResourceProvider;
        }

        //
        #region Loaders ...
        /// <summary>
        /// Load Projects List of Specified Owner ...
        /// </summary>
        /// <param name="ownerId"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<IEnumerable<XAiProjectDto>> LoadProjects(
            string ownerId,
            bool fillConversations = false,
            bool fillConversationMessages = false,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Validate Args ...
            if (ownerId.IsNullOrEmpty())
            {
                XException.InvalidArgs.Throw();
            }

            //
            // Handle Default Project and Conversation ...
            bool hasDefault = await HasDefaultProject(
                ownerId: ownerId,
                cancellationToken: cancellationToken
            );
            if (!hasDefault)
            {
                //
                await CreateDefaultProject(
                    ownerId: ownerId,
                    connectionId: null,
                    cancellationToken: cancellationToken
                );
            }

            //
            // Retrieve Project List ...
            var result = await projectRepositoryService.FindManyAsync(
                includeBuilder: null,
                ignoreSoftDeleteds: true,
                // fillConversations: fillConversations,
                predicate: x => x.OwnerId == ownerId,
                cancellationToken: cancellationToken,
                orderBuilder: x => x.OrderBy(y => y.CreatedOn)
                // fillConversationMessages: fillConversationMessages
            );

            //
            return result;
        }

        /// <summary>
        /// Load List of Conversations for Specified Project ... 
        /// </summary>
        /// <param name="projectId"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<IEnumerable<XAiConversationDto>> LoadConversations(
            Guid projectId,
            bool fillMessages = false,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Validate Args ...
            if (projectId.IsNull() ||
                projectId.IsDefaultGuid()
            )
            {
                XException.InvalidArgs.Throw();
            }

            //
            // Retrieve Conversation's List ...
            var result = await conversationRepositoryService.FindManyAsync(
                includeBuilder: null,
                ignoreSoftDeleteds: false,
                // fillMessages: fillMessages,
                cancellationToken: cancellationToken,
                predicate: x => x.ProjectId == projectId,
                orderBuilder: x => x.OrderBy(y => y.CreatedOn)
            );

            //
            return result;
        }

        /// <summary>
        /// Load Messages of Specified Conversation ...
        /// </summary>
        /// <param name="conversationId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<IEnumerable<XAiMessageDto>> LoadMessages(
            Guid conversationId,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Validate Args ...
            if (conversationId.IsNull() ||
                conversationId.IsDefaultGuid()
            )
            {
                XException.InvalidArgs.Throw();
            }

            //
            // Retrieve Message Lists of Specified Conversation ...
            var result = await messageRepositoryService.FindManyAsync(
                includeBuilder: null,
                ignoreSoftDeleteds: true,
                cancellationToken: cancellationToken,
                orderBuilder: x => x.OrderBy(y => y.CreatedOn),
                predicate: x => x.ConversationId == conversationId
            );

            //
            return result;
        }
        #endregion

        //
        #region Creator/Adders ...
        public async Task<XAiProjectDto> CreateProject(
            string ownerId,
            XAiProjectDto item = null,
            string connectionId = null,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Validate Args ...
            if (ownerId.IsNullOrEmpty())
            {
                XException.InvalidArgs.Throw();
            }

            //
            // Check Project Provided or not ...
            var hasProject = !item.IsNullOrDefault();

            //
            // Normalize Provided Project ...
            if (hasProject)
            {
                //
                item.CreatedOn = DateTime.UtcNow;

                //
                // Validate Item ...
                if (!item.IsValidForAddOrUpdate())
                {
                    XException.InvalidData.Throw();
                }
            }

            //
            XAiProjectDto result = null;

            //
            // Create Provided Project ...
            if (hasProject)
            {
                //
                result = await projectRepositoryService.AddAsync(
                    item: item,
                    saveChanges: true,
                    cancellationToken: cancellationToken
                );

                //
                // Handling Send Push Notification ...
                if (!result.IsNullOrDefault() &&
                    !connectionId.IsNullOrEmpty()
                )
                {
                    //
                    await SendPush(
                        connectionId: connectionId,
                        cancellationToken: cancellationToken,
                        payLoad: item.ToJSON(camelCase: true),
                        action: XAiDataProviderHubActions.ProjectCreated.GetStringValue()
                    );
                }
            }
            //
            // Check Default Project ...
            else
            {
                //
                // Check Default Project Exists or not ...
                var hasDefault = await HasDefaultProject(
                    ownerId: ownerId,
                    cancellationToken: cancellationToken
                );

                //
                // If Default Project Exists, Throw Exception ...
                if (hasDefault)
                {
                    XException.InvalidArgs.Throw();
                }

                //
                // Create and Retrive Default Project ...
                result = await GetDefaultProject(
                    ownerId: ownerId,
                    connectionId: connectionId,
                    cancellationToken: cancellationToken
                );
            }

            //
            // Validate ...
            if (result.IsNullOrDefault())
            {
                XException.ActionFailed.Throw();
            }

            //
            return result;
        }

        public async Task<XAiConversationDto> CreateConversation(
            Guid projectId,
            XAiConversationDto item = null,
            string connectionId = null,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Validate Args ...
            if (projectId.IsNull() ||
                projectId.IsDefaultGuid())
            {
                XException.InvalidArgs.Throw();
            }

            //
            // Handle Proejct ...
            XAiProjectDto project = await projectRepositoryService.GetAsync(
                id: projectId,
                includeBuilder: null,
                ignoreSoftDeleteds: true,
                // fillConversations: false,
                // fillConversationMessages: false,
                cancellationToken: cancellationToken
            );
            if (project.IsNullOrDefault())
            {
                XException.NotFound.Throw();
            }

            //
            // Check Conversation Provided or not ...
            var hasConversation = !item.IsNullOrDefault();

            //
            // Normalize Conversation if Provided ...
            if (hasConversation)
            {
                //
                item.ProjectId = project.Id;
                item.CreatedOn = DateTime.UtcNow;

                //
                // Validate Item ...
                if (!item.IsValidForAddOrUpdate())
                {
                    XException.InvalidData.Throw();
                }
            }

            //
            XAiConversationDto result = null;

            //
            // Create Provided Conversation ...
            if (hasConversation)
            {
                //
                result = await conversationRepositoryService.AddAsync(
                    item: item,
                    saveChanges: true,
                    // fillMessages: false,
                    cancellationToken: cancellationToken
                );

                //
                // Handling Send Push Notification ...
                if (!result.IsNullOrDefault() &&
                    !connectionId.IsNullOrEmpty()
                )
                {
                    //
                    await SendPush(
                        connectionId: connectionId,
                        cancellationToken: cancellationToken,
                        payLoad: item.ToJSON(camelCase: true),
                        action: XAiDataProviderHubActions.ConversationCreated.GetStringValue()
                    );
                }
            }
            //
            // Check Default Conversation ...
            else
            {
                //
                // Check Default Conversation Exists or not ...
                var hasDefault = await HasDefaultConversation(
                    projectId: project.Id,
                    ownerId: project.OwnerId,
                    cancellationToken: cancellationToken
                );

                //
                // If Default Project Exists, Throw Exception ...
                if (hasDefault)
                {
                    XException.InvalidArgs.Throw();
                }

                //
                // Create and Retrive Default Conversation ...
                result = await GetDefaultConversation(
                    projectId: project.Id,
                    ownerId: project.OwnerId,
                    connectionId: connectionId,
                    cancellationToken: cancellationToken
                );
            }

            //
            // Validate ...
            if (result.IsNullOrDefault())
            {
                XException.ActionFailed.Throw();
            }

            //
            return result;
        }

        public async Task<XAiMessageDto> AddMessage(
            Guid conversationId,
            XAiMessageDto item,
            string connectionId = null,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Validate Args ...
            if (item.IsNullOrDefault() ||
                conversationId.IsNull() ||
                conversationId.IsDefaultGuid()
            )
            {
                XException.InvalidArgs.Throw();
            }

            //
            // Normalize ...
            item.CreatedOn = DateTime.UtcNow;
            item.ConversationId = conversationId;

            //
            // Validate Item ...
            if (!item.IsValidForAddOrUpdate())
            {
                XException.InvalidData.Throw();
            }

            //
            var result = await messageRepositoryService.AddAsync(
                item: item,
                saveChanges: true,
                cancellationToken: cancellationToken
            );

            //
            // Handling Send Push Notification ...
            if (!result.IsNullOrDefault() &&
                !connectionId.IsNullOrEmpty()
            )
            {
                //
                await SendPush(
                    connectionId: connectionId,
                    cancellationToken: cancellationToken,
                    payLoad: item.ToJSON(camelCase: true),
                    action: XAiDataProviderHubActions.MessageCreated.GetStringValue()
                );
            }

            //
            return result;
        }
        #endregion

        //
        #region Updaters ...
        #endregion

        //
        #region Private ...
        /// <summary>
        /// Check Default Project Exists or not ...
        /// </summary>
        /// <param name="ownerId"></param>
        /// <param name="connectionId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        private async Task<bool> HasDefaultProject(
            string ownerId,
            string connectionId = null,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Validate Args ...
            var result = !ownerId.IsNullOrEmpty();
            if (!result)
            {
                return result;
            }

            //
            // Check Default Owner Exists or not ...
            result = !(await stringProvider.FindOne(
                cancellationToken: cancellationToken,
                predicate: x =>
                    x.ResourceTitle
                        .ToNormalString()
                        .Contains(ownerId.ToNormalString()) &&
                    x.ResourceTitle
                        .ToNormalString()
                        .Contains(XAiModelsConstants.XAiProjectTitleIdentifier
                            .ToNormalString()) &&
                    x.TranslatedValue
                    .ToNormalString()
                    .Contains(XAiDataProviderConstants.XDefaultAiProjectSuffix)
            ))
            .IsNullOrDefault();
            return result;
        }

        /// <summary>
        /// Retrieve Default Project of Specified Owner ...
        /// Create it if not Handled ...
        /// </summary>
        /// <param name="ownerId"></param>
        /// <param name="connectionId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        private async Task<XAiProjectDto> GetDefaultProject(
            string ownerId,
            string connectionId = null,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Check Default Project Exists or noit ...
            var isExists = await HasDefaultProject(
                ownerId: ownerId,
                connectionId: connectionId,
                cancellationToken: cancellationToken
            );
            if (!isExists)
            {
                //
                await CreateDefaultProject(
                    ownerId: ownerId,
                    connectionId: connectionId,
                    cancellationToken: cancellationToken
                );
            }

            //
            var resource = await stringProvider.FindOne(
                cancellationToken: cancellationToken,
                predicate: x =>
                    x.ResourceTitle
                        .ToNormalString()
                        .Contains(ownerId.ToNormalString()) &&
                    x.ResourceTitle
                        .ToNormalString()
                        .Contains(XAiModelsConstants.XAiProjectTitleIdentifier
                            .ToNormalString()) &&
                    x.TranslatedValue
                    .ToNormalString()
                    .Contains(XAiDataProviderConstants.XDefaultAiProjectSuffix)
            );
            if (resource.IsNullOrDefault())
            {
                XException.NotFound.Throw();
            }

            //
            var result = await projectRepositoryService.FindOneAsync(
                cancellationToken: cancellationToken,
                predicate: x => x.Title
                    .Equals(
                        resource.ResourceTitle,
                        StringComparison.InvariantCultureIgnoreCase
                    )
            );
            if (result.IsNullOrDefault())
            {
                XException.NotFound.Throw();
            }

            //
            return result;
        }

        /// <summary>
        /// Create Default Project for Specified Owner ... 
        /// </summary>
        /// <param name="ownerId"></param>
        /// <param name="connectionId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        private async Task CreateDefaultProject(
            string ownerId,
            string connectionId = null,
            CancellationToken cancellationToken = default
        )
        {
            //
            if (ownerId.IsNullOrEmpty())
            {
                return;
            }

            //
            var defaultLang = appConfiguration.DefaultLanguage;

            //
            // Check Default Owner Exists or not ...
            var isExists = await HasDefaultProject(
                ownerId: ownerId,
                cancellationToken: cancellationToken
            );
            if (isExists)
            {
                return;
            }

            //
            // Create Default ...
            var item = new XAiProjectDto()
            {
                OwnerId = ownerId,
                CreatedOn = DateTime.UtcNow,
                Title = Guid.NewGuid().ToString(),
                Description = Guid.NewGuid().ToString(),
            };
            item = await projectRepositoryService.AddAsync(
                item: item,
                saveChanges: true,
                cancellationToken: cancellationToken
            );

            //
            // Prepare Tile and Description Resource Ids ...
            var titleResource = await projectTitleResourceProvider.AddOrUpdateLocale(
                owner: ownerId,
                identifier: item.Id,
                locale: new XLocaleResourceDto
                {
                    Language = defaultLang,
                    Value = XAiDataProviderConstants.XDefaultAiProjectSuffix
                },
                cancellationToken: cancellationToken
            );
            var descriptionResource = await projectDescriptionResourceProvider.AddOrUpdateLocale(
                owner: ownerId,
                identifier: item.Id,
                new XLocaleResourceDto
                {
                    Language = defaultLang,
                    Value = XAiDataProviderConstants.XDefaultAiProjectSuffix
                },
                cancellationToken: cancellationToken
            );
            if (titleResource.IsNullOrDefault() ||
                descriptionResource.IsNullOrDefault()
            )
            {
                return;
            }

            //
            // Update Project Resources ...
            item.Title = titleResource.Resource;
            item.Description = descriptionResource.Resource;
            item = await projectRepositoryService.UpdateAsync(
                id: item.Id,
                item: item,
                saveChanges: true,
                cancellationToken: cancellationToken
            );

            //
            // Sending Push ...
            if (!connectionId.IsNullOrEmpty())
            {
                //
                await SendPush(
                    connectionId: connectionId,
                    cancellationToken: cancellationToken,
                    payLoad: item.ToJSON(camelCase: true),
                    action: XAiDataProviderHubActions.ProjectCreated.GetStringValue()
                );
            }
        }

        /// <summary>
        /// Check Default Conversation Exists or not ...
        /// </summary>
        /// <param name="ownerId"></param>
        /// <param name="projectId"></param>
        /// <param name="connectionId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        private async Task<bool> HasDefaultConversation(
            string ownerId,
            Guid projectId,
            string connectionId = null,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Validate Args ...
            var result = !ownerId.IsNullOrEmpty();
            if (!result)
            {
                return result;
            }

            //
            // Prepare Project ...
            XAiProjectDto project = null;
            if (projectId.IsNull() ||
                projectId.IsDefaultGuid()
            )
            {
                //
                project = await GetDefaultProject(
                    ownerId: ownerId,
                    connectionId: connectionId,
                    cancellationToken: cancellationToken
                );
            }
            else
            {
                //
                project = await projectRepositoryService.GetAsync(
                    id: project.Id,
                    includeBuilder: null,
                    ignoreSoftDeleteds: true,
                    // fillConversations: false,
                    // fillConversationMessages: false,
                    cancellationToken: cancellationToken
                );
            }
            if (project.IsNullOrDefault())
            {
                XException.NotFound.Throw();
            }

            //
            // Check Default Owner Exists or not ...
            var resource = await stringProvider.FindOne(
                cancellationToken: cancellationToken,
                predicate: x =>
                    x.ResourceTitle
                        .ToNormalString()
                        .Contains(ownerId.ToNormalString()) &&
                    x.ResourceTitle
                        .ToNormalString()
                        .Contains(XAiModelsConstants.XAiConversationTitleIdentifier
                            .ToNormalString()) &&
                    x.TranslatedValue
                    .ToNormalString()
                    .Contains(XAiDataProviderConstants.XDefaultAiConversationSuffix)
            );
            result = !resource.IsNullOrDefault();
            if (!result)
            {
                return result;
            }

            //
            result = !(await conversationRepositoryService.FindOneAsync(
                cancellationToken: cancellationToken,
                predicate: x =>
                    x.ProjectId == projectId &&
                    x.Title == resource.ResourceTitle
            ))
            .IsNullOrDefault();

            //
            return result;
        }

        /// <summary>
        /// Check Default Conversation Exists or not ...
        /// </summary>
        /// <param name="ownerId"></param>
        /// <param name="projectId"></param>
        /// <param name="connectionId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        private async Task CreateDefaultConversation(
            string ownerId,
            Guid projectId,
            string connectionId = null,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Validate Args ...
            var isValid = !ownerId.IsNullOrEmpty();
            if (!isValid)
            {
                return;
            }

            //
            var defaultLang = appConfiguration.DefaultLanguage;

            //
            // Prepare Project ...
            XAiProjectDto project = null;
            if (projectId.IsNull() ||
                projectId.IsDefaultGuid()
            )
            {
                //
                project = await GetDefaultProject(
                    ownerId: ownerId,
                    connectionId: connectionId,
                    cancellationToken: cancellationToken
                );
            }
            else
            {
                //
                project = await projectRepositoryService.GetAsync(
                    id: project.Id,
                    includeBuilder: null,
                    ignoreSoftDeleteds: true,
                    // fillConversations: false,
                    // fillConversationMessages: false,
                    cancellationToken: cancellationToken
                );
            }
            if (project.IsNullOrDefault())
            {
                XException.NotFound.Throw();
            }

            //
            // Check Default Owner Exists or not ...
            isValid = await HasDefaultConversation(
                ownerId: ownerId,
                projectId: project.Id,
                cancellationToken: cancellationToken
            );
            if (isValid)
            {
                return;
            }

            //
            // Create Default Conversation ...
            var item = new XAiConversationDto
            {
                OwnerId = ownerId,
                ProjectId = project.Id,
                CreatedOn = DateTime.UtcNow,
                Title = Guid.NewGuid().ToString()
            };
            item = await conversationRepositoryService.AddAsync(
                item: item,
                saveChanges: true,
                cancellationToken: cancellationToken
            );

            //
            // Prepare Title Resource Ids ...
            var titleResource = await conversationTitleResourceProvider.AddOrUpdateLocale(
                owner: ownerId,
                identifier: item.Id,
                locale: new XLocaleResourceDto
                {
                    Language = defaultLang,
                    Value = XAiDataProviderConstants.XDefaultAiConversationSuffix
                },
                cancellationToken: cancellationToken
            );
            if (titleResource.IsNullOrDefault())
            {
                return;
            }

            //
            // Update Conversation Resources ...
            item.Title = titleResource.Resource;
            item = await conversationRepositoryService.UpdateAsync(
                item: item,
                id: item.Id,
                saveChanges: true,
                cancellationToken: cancellationToken
            );

            //
            // Sending Push ...
            if (!connectionId.IsNullOrEmpty())
            {
                //
                await SendPush(
                    connectionId: connectionId,
                    cancellationToken: cancellationToken,
                    payLoad: item.ToJSON(camelCase: true),
                    action: XAiDataProviderHubActions.ConversationCreated.GetStringValue()
                );
            }
        }

        /// <summary>
        /// Retrieve Default Conversation of Specified Owner ...
        /// Create it if not Handled ...
        /// </summary>
        /// <param name="ownerId"></param>
        /// <param name="projectId"></param>
        /// <param name="connectionId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        private async Task<XAiConversationDto> GetDefaultConversation(
            string ownerId,
            Guid projectId,
            string connectionId = null,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Validate Args ...
            if (ownerId.IsNullOrEmpty())
            {
                XException.InvalidArgs.Throw();
            }

            //
            // Prepare Project ...
            XAiProjectDto project = null;
            if (projectId.IsNull() ||
                projectId.IsDefaultGuid()
            )
            {
                //
                project = await GetDefaultProject(
                    ownerId: ownerId,
                    connectionId: connectionId,
                    cancellationToken: cancellationToken
                );
            }
            else
            {
                //
                project = await projectRepositoryService.GetAsync(
                    id: project.Id,
                    includeBuilder: null,
                    ignoreSoftDeleteds: true,
                    // fillConversations: false,
                    // fillConversationMessages: false,
                    cancellationToken: cancellationToken
                );
            }
            if (project.IsNullOrDefault())
            {
                XException.NotFound.Throw();
            }

            //
            // Check Default Conversation Exists or noit ...
            var isExists = await HasDefaultConversation(
                ownerId: ownerId,
                projectId: project.Id,
                connectionId: connectionId,
                cancellationToken: cancellationToken
            );
            if (!isExists)
            {
                //
                await CreateDefaultConversation(
                    ownerId: ownerId,
                    projectId: project.Id,
                    connectionId: connectionId,
                    cancellationToken: cancellationToken
                );
            }

            //
            // Detect Conversation Resource ...
            var resource = await stringProvider.FindOne(
                cancellationToken: cancellationToken,
                predicate: x =>
                    x.ResourceTitle
                        .ToNormalString()
                        .Contains(ownerId.ToNormalString()) &&
                    x.ResourceTitle
                        .ToNormalString()
                        .Contains(XAiModelsConstants.XAiProjectTitleIdentifier
                            .ToNormalString()) &&
                    x.TranslatedValue
                    .ToNormalString()
                    .Contains(XAiDataProviderConstants.XDefaultAiProjectSuffix)
            );
            if (resource.IsNullOrDefault())
            {
                XException.NotFound.Throw();
            }

            //
            // Find Default Conversation ...
            var result = await conversationRepositoryService.FindOneAsync(
                cancellationToken: cancellationToken,
                predicate: x => x.Title
                    .Equals(
                        resource.ResourceTitle,
                        StringComparison.InvariantCultureIgnoreCase
                    ) &&
                    x.ProjectId == project.Id
            );
            if (result.IsNullOrDefault())
            {
                XException.NotFound.Throw();
            }

            //
            return result;
        }

        /// <summary>
        /// Normalize and Validate Language ...
        /// </summary>
        /// <param name="language"></param>
        /// <returns></returns>
        private string NormalizeAndValidateLanguage(string language)
        {
            //
            if (language.IsNullOrEmpty())
            {
                language = appConfiguration.DefaultLanguage;
            }

            //
            if (language.IsNullOrEmpty())
            {
                XException.InvalidArgs.Throw();
            }

            //
            return language;
        }

        /// <summary>
        /// Send Custom Push Message ...
        /// </summary>
        /// <param name="action"></param>
        /// <param name="payLoad"></param>
        /// <param name="connectionId"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task SendPush(
            string action,
            string payLoad,
            string connectionId = null,
            CancellationToken cancellationToken = default
        )
        {
            //
            try
            {
                //
                // Collect Allowed Actions ...
                var actions = new List<string>
                {
                    XAiDataProviderHubActions.ProjectCreated.GetStringValue(),
                    XAiDataProviderHubActions.ProjectUpdated.GetStringValue(),
                    XAiDataProviderHubActions.ProjectRemoved.GetStringValue(),
                    XAiDataProviderHubActions.MessageCreated.GetStringValue(),
                    XAiDataProviderHubActions.MessageUpdated.GetStringValue(),
                    XAiDataProviderHubActions.MessageRemoved.GetStringValue(),
                    XAiDataProviderHubActions.ConversationCreated.GetStringValue(),
                    XAiDataProviderHubActions.ConversationUpdated.GetStringValue(),
                    XAiDataProviderHubActions.ConversationRemoved.GetStringValue(),
                };

                //
                // Validate Args ...
                var isValid =
                    !action.IsNullOrEmpty() &&
                    actions.Contains(action);
                if (!isValid)
                {
                    return;
                }

                //
                // Retrieve Connection Id ...
                var clients = hub.Clients.All;
                if (!connectionId.IsNullOrEmpty())
                {
                    clients = hub.Clients.AllExcept(connectionId);
                }

                //
                await clients.SendAsync(
                    action,
                    payLoad,
                    connectionId,
                    cancellationToken
                );
            }
            catch { }
        }
        #endregion
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\XAiMessageEntityRegisterar.cs

using System;
using xDataService.Providers;
using xDataService.Interfaces;
using xAiModels.Models.Entities;
using Microsoft.EntityFrameworkCore;
using xAiModels.Configurations.Entities;

namespace xAiModels.Providers
{
    public class XAiMessageEntityRegisterar : XBaseEntityRegisterar, IXEntityRegisterar
    {
        public XAiMessageEntityRegisterar()
        : base(nameof(XAiMessageEntityRegisterar))
        {
            //
            // Add Entity ...
            AddEntity<XAiMessage, Guid>();
        }

        /// <summary>
        /// Configure Entities ...
        /// </summary>
        /// <param name="modelBuilder"></param>
        public override void ConfigureEntities(ModelBuilder modelBuilder)
        {
            //
            // Configure Entity Using Provided ...
            modelBuilder
                .ApplyConfigurationsFromAssembly(typeof(XAiMessageEntityConfiguration).Assembly);

            //
            base.ConfigureEntities(modelBuilder);
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\XAiProjectDescriptionResourceProvider.cs

using xAiModels.Constants;
using xAiModels.Interfaces;
using xCommons.Configurations;
using xStringService.Providers;
using xStringService.Interfaces.Dtos;

namespace xAiModels.Providers
{
    public class XAiProjectDescriptionResourceProvider : XBaseOwnedItemResourceProvider, IXAiProjectDescriptionResourceProvider
    {
        public XAiProjectDescriptionResourceProvider(
            XAppConfiguration appConfig,
            IXStringServiceProvider stringProvider
        ) : base(
            appConfig: appConfig,
            stringProvider: stringProvider,
            item: XAiModelsConstants.XAiProjectDescriptionIdentifier
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\XAiProjectEntityRegisterar.cs

using System;
using xDataService.Providers;
using xDataService.Interfaces;
using xAiModels.Models.Entities;
using Microsoft.EntityFrameworkCore;
using xAiModels.Configurations.Entities;

namespace xAiModels.Providers
{
    public class XAiProjectEntityRegisterar : XBaseEntityRegisterar, IXEntityRegisterar
    {
        public XAiProjectEntityRegisterar()
        : base(nameof(XAiProjectEntityRegisterar))
        {
            //
            // Add Entity ...
            AddEntity<XAiProject, Guid>();
        }

        /// <summary>
        /// Configure Entities ...
        /// </summary>
        /// <param name="modelBuilder"></param>
        public override void ConfigureEntities(ModelBuilder modelBuilder)
        {
            //
            // Configure Entity Using Provided ...
            modelBuilder
                .ApplyConfigurationsFromAssembly(typeof(XAiProjectEntityConfiguration).Assembly);

            //
            base.ConfigureEntities(modelBuilder);
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\XAiProjectTitleResourceProvider.cs

using xAiModels.Constants;
using xAiModels.Interfaces;
using xCommons.Configurations;
using xStringService.Providers;
using xStringService.Interfaces.Dtos;

namespace xAiModels.Providers
{
    public class XAiProjectTitleResourceProvider : XBaseOwnedItemResourceProvider, IXAiProjectTitleResourceProvider
    {
        public XAiProjectTitleResourceProvider(
            XAppConfiguration appConfig,
            IXStringServiceProvider stringProvider
        ) : base(
            appConfig: appConfig,
            stringProvider: stringProvider,
            item: XAiModelsConstants.XAiProjectTitleIdentifier
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Dtos\XAiConversationDtoHub.cs

using System;
using xPushService.Base;
using xAiModels.Models.Dtos;
using xAiModels.Interfaces.Dtos;
using xAiModels.Models.Entities;
using Microsoft.Extensions.Logging;

namespace xAiModels.Providers.Dtos
{
    public class XAiConversationDtoHub : XBaseDtoHub<XAiConversation, XAiConversationDto, Guid>, IXAiConversationDtoHub
    {
        public XAiConversationDtoHub(
            ILogger<XAiConversationDtoHub> logger
        ) : base(logger)
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Dtos\XAiConversationRepositoryService.cs

using System;
using System.Linq;
using xModels.Dtos;
using System.Threading;
using xCommons.Extensions;
using xAiModels.Interfaces;
using xAiModels.Models.Dtos;
using System.Threading.Tasks;
using xDataService.Providers;
using xAiModels.Configurations;
using xAiModels.Interfaces.Dtos;
using xAiModels.Models.Entities;
using System.Collections.Generic;
using xIdentityService.Interfaces;
using xIdentityService.Extensions;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.Dtos
{
    public class XAiConversationRepositoryService : XBaseRepositoryService<XAiConversation, XAiConversationDto, Guid>, IXAiConversationRepositoryService
    {
        private readonly IXIdentityProvider identityProvider;
        private readonly IXAiConversationRepository repository;
        private readonly IXAiProjectTitleResourceProvider projectTitleResourceProvider;
        private readonly IXAiConversationTitleResourceProvider conversationTitleResourceProvider;
        private readonly IXAiProjectDescriptionResourceProvider projectDescriptionResourceProvider;

        public XAiConversationRepositoryService(
            IXIdentityProvider identityProvider,
            IXAiConversationRepository repository,
            IXAiProjectTitleResourceProvider projectTitleResourceProvider,
            IXAiConversationTitleResourceProvider conversationTitleResourceProvider,
            IXAiProjectDescriptionResourceProvider projectDescriptionResourceProvider
        ) : base(
            repository,
            mapperProfiles:
            [
                new XAiModelMappersProfile()
            ]
        )
        {
            this.repository = repository;
            this.identityProvider = identityProvider;
            this.projectTitleResourceProvider = projectTitleResourceProvider;
            this.conversationTitleResourceProvider = conversationTitleResourceProvider;
            this.projectDescriptionResourceProvider = projectDescriptionResourceProvider;
        }

        //
        #region Enrichers ...
        /// <summary>
        /// Enrich Specified Item ...
        /// </summary>
        /// <param name="source"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<XAiConversationDto> Enrich(
            XAiConversation source,
            bool fillMessages = false,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Mapping Default ...
            var result = Mapper.Map<XAiConversationDto>(source);
            result = await Enrich(
                source: result,
                fillMessages: fillMessages,
                cancellationToken: cancellationToken
            );

            //
            return result;
        }

        /// <summary>
        /// Enrich Specified Item ...
        /// </summary>
        /// <param name="source"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<XAiConversationDto> Enrich(
            XAiConversationDto source,
            bool fillMessages = false,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Handdle Owner ...
            XPersonDto owner = null;
            if (!source.OwnerId.IsNullOrEmpty())
            {
                //
                // Try to Retrieve User Info ...
                var device = identityProvider.GetDevice();
                var userInfo = await identityProvider.GetUserInfo(
                    device: device,
                    userSelectByParam: source.OwnerId
                );
                owner = userInfo.ToXPersonDto();
            }

            //
            // Handle Title Resource ...
            XResourceDto titleResource = null;
            if (!source.Title.IsNullOrEmpty())
            {
                //
                titleResource = await conversationTitleResourceProvider.GetResource(
                    owner: source.OwnerId,
                    identifier: source.Id,
                    cancellationToken: cancellationToken
                );
            }

            //
            // Handle Project Info ...
            XResourceDto projectTitleResource = null;
            XResourceDto projectDescriptionResource = null;
            if (!source.ProjectId.IsDefaultGuid())
            {
                //
                projectTitleResource = await projectTitleResourceProvider.GetResource(
                    owner: source.OwnerId,
                    identifier: source.ProjectId,
                    cancellationToken: cancellationToken
                );

                //
                projectDescriptionResource = await projectDescriptionResourceProvider.GetResource(
                    owner: source.OwnerId,
                    identifier: source.ProjectId,
                    cancellationToken: cancellationToken
                );
            }

            //
            // Attach Extracted Items ...
            var result = Mapper.Map<XAiConversationDto>(source, options =>
            {
                //
                options.Items[nameof(source.Owner)] = owner;
                options.Items[nameof(source.TitleResource)] = titleResource;
                options.Items[nameof(source.ProjectTitleResource)] = projectTitleResource;
                options.Items[nameof(source.ProjectDescriptionResource)] = projectDescriptionResource;
            });

            //
            return result;
        }

        /// <summary>
        /// Enrich Specified Items ...
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<IEnumerable<XAiConversationDto>> EnrichRange(
            IEnumerable<XAiConversation> sources,
            bool fillMessages = false,
            CancellationToken cancellationToken = default
        )
        {
            //
            var tasks = sources
                .Select(x => Enrich(
                    source: x,
                    fillMessages: fillMessages,
                    cancellationToken: cancellationToken
                ));

            //
            var result = await Task.WhenAll(tasks);
            return result;
        }

        /// <summary>
        /// Enrich Specified Items ...
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<IEnumerable<XAiConversationDto>> EnrichRange(
            IEnumerable<XAiConversationDto> sources,
            bool fillMessages = false,
            CancellationToken cancellationToken = default
        )
        {
            //
            var tasks = sources
                .Select(x => Enrich(
                    source: x,
                    fillMessages: fillMessages,
                    cancellationToken: cancellationToken
                ));

            //
            var result = await Task.WhenAll(tasks);
            return result;
        }
        #endregion                
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Dtos\XAiConversationServiceProvider.cs

using System;
using xPushService.Base;
using xAiModels.Models.Dtos;
using xAiModels.Interfaces.Dtos;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xIdentityService.Interfaces;
using Microsoft.AspNetCore.SignalR;

namespace xAiModels.Providers.Dtos
{
    public class XAiConversationServiceProvider : XBaseDtoProvider<XAiConversation, XAiConversationDto, Guid, XAiConversationDtoHub>, IXAiConversationServiceProvider
    {
        public XAiConversationServiceProvider(
            IHubContext<XAiConversationDtoHub> hub,
            XDataServiceConfiguration dataConfiguration,
            IXAiConversationRepositoryService service,
            IXIdentityProvider identityProvider = null
        ) : base(
            hub,
            dataConfiguration,
            service,
            identityProvider
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Dtos\XAiMessageDtoHub.cs

using System;
using xPushService.Base;
using xAiModels.Models.Dtos;
using xAiModels.Interfaces.Dtos;
using xAiModels.Models.Entities;
using Microsoft.Extensions.Logging;

namespace xAiModels.Providers.Dtos
{
    public class XAiMessageDtoHub : XBaseDtoHub<XAiMessage, XAiMessageDto, Guid>, IXAiMessageDtoHub
    {
        public XAiMessageDtoHub(
            ILogger<XAiMessageDtoHub> logger
        ) : base(logger)
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Dtos\XAiMessageRepositoryService.cs

using System;
using System.Linq;
using xModels.Dtos;
using System.Threading;
using xCommons.Extensions;
using xAiModels.Interfaces;
using xAiModels.Models.Dtos;
using System.Threading.Tasks;
using xDataService.Providers;
using xAiModels.Configurations;
using xAiModels.Interfaces.Dtos;
using xAiModels.Models.Entities;
using System.Collections.Generic;
using xIdentityService.Interfaces;
using xIdentityService.Extensions;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.Dtos
{
    public class XAiMessageRepositoryService : XBaseRepositoryService<XAiMessage, XAiMessageDto, Guid>, IXAiMessageRepositoryService
    {
        private readonly IXIdentityProvider identityProvider;
        private readonly IXAiConversationTitleResourceProvider conversationTitleResourceProvider;

        public XAiMessageRepositoryService(
            IXAiMessageRepository repository,
            IXIdentityProvider identityProvider,
            IXAiConversationTitleResourceProvider conversationTitleResourceProvider
        ) : base(
            repository,
            mapperProfiles:
            [
                new XAiModelMappersProfile()
            ]
        )
        {
            this.identityProvider = identityProvider;
            this.conversationTitleResourceProvider = conversationTitleResourceProvider;
        }

        //
        #region Enrichers ...
        /// <summary>
        /// Enrich Specified Item ...
        /// </summary>
        /// <param name="source"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<XAiMessageDto> Enrich(
            XAiMessage source,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Mapping Default ...
            var result = Mapper.Map<XAiMessageDto>(source);
            result = await Enrich(
                source: result,
                cancellationToken: cancellationToken
            );

            //
            return result;
        }

        /// <summary>
        /// Enrich Specified Item ...
        /// </summary>
        /// <param name="source"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<XAiMessageDto> Enrich(
            XAiMessageDto source,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Handdle Owner ...
            XPersonDto owner = null;
            if (!source.OwnerId.IsNullOrEmpty())
            {
                //
                // Try to Retrieve User Info ...
                var device = identityProvider.GetDevice();
                var userInfo = await identityProvider.GetUserInfo(
                    device: device,
                    userSelectByParam: source.OwnerId
                );
                owner = userInfo.ToXPersonDto();
            }

            //
            // Hanlde Conversation Title ...
            string conversationTitle = string.Empty;
            XResourceDto conversationTitleResource = null;
            if (!source.ConversationId.IsDefaultGuid())
            {
                //
                conversationTitleResource = await conversationTitleResourceProvider.GetResource(
                    owner: source.OwnerId,
                    identifier: source.ConversationId,
                    cancellationToken: cancellationToken
                );
                if (!conversationTitleResource.IsNullOrDefault())
                {
                    conversationTitle = conversationTitleResource.Resource;
                }
            }

            //
            // Attach Extracted Items ...
            var result = Mapper.Map<XAiMessageDto>(source, options =>
            {
                //
                options.Items[nameof(source.Owner)] = owner;
                options.Items[nameof(source.ConversationTitle)] = conversationTitle;
                options.Items[nameof(source.ConversationTitleResouce)] = conversationTitleResource;
            });

            //
            return result;
        }

        /// <summary>
        /// Enrich Specified Items ...
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<IEnumerable<XAiMessageDto>> EnrichRange(
            IEnumerable<XAiMessage> sources,
            CancellationToken cancellationToken = default
        )
        {
            //
            var tasks = sources
                .Select(x => Enrich(x, cancellationToken));

            //
            var result = await Task.WhenAll(tasks);
            return result;
        }

        /// <summary>
        /// Enrich Specified Items ...
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<IEnumerable<XAiMessageDto>> EnrichRange(
            IEnumerable<XAiMessageDto> sources,
            CancellationToken cancellationToken = default
        )
        {
            //
            var tasks = sources
                .Select(x => Enrich(x, cancellationToken));

            //
            var result = await Task.WhenAll(tasks);
            return result;
        }
        #endregion
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Dtos\XAiMessageServiceProvider.cs

using System;
using xPushService.Base;
using xAiModels.Models.Dtos;
using xAiModels.Interfaces.Dtos;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xIdentityService.Interfaces;
using Microsoft.AspNetCore.SignalR;

namespace xAiModels.Providers.Dtos
{
    public class XAiMessageServiceProvider : XBaseDtoProvider<XAiMessage, XAiMessageDto, Guid, XAiMessageDtoHub>, IXAiMessageServiceProvider
    {
        public XAiMessageServiceProvider(
            IHubContext<XAiMessageDtoHub> hub,
            XDataServiceConfiguration dataConfiguration,
            IXAiMessageRepositoryService service,
            IXIdentityProvider identityProvider = null
        ) : base(
            hub,
            dataConfiguration,
            service,
            identityProvider
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Dtos\XAiProjectDtoHub.cs

using System;
using xPushService.Base;
using xAiModels.Models.Dtos;
using xAiModels.Interfaces.Dtos;
using xAiModels.Models.Entities;
using Microsoft.Extensions.Logging;

namespace xAiModels.Providers.Dtos
{
    public class XAiProjectDtoHub : XBaseDtoHub<XAiProject, XAiProjectDto, Guid>, IXAiProjectDtoHub
    {
        public XAiProjectDtoHub(
            ILogger<XAiProjectDtoHub> logger
        ) : base(logger)
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Dtos\XAiProjectRepositoryService.cs

using System;
using System.Linq;
using xModels.Dtos;
using System.Threading;
using xCommons.Extensions;
using xAiModels.Interfaces;
using xAiModels.Models.Dtos;
using System.Threading.Tasks;
using xDataService.Providers;
using xAiModels.Configurations;
using xAiModels.Interfaces.Dtos;
using xAiModels.Models.Entities;
using System.Collections.Generic;
using xIdentityService.Interfaces;
using xIdentityService.Extensions;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.Dtos
{
    public class XAiProjectRepositoryService : XBaseRepositoryService<XAiProject, XAiProjectDto, Guid>, IXAiProjectRepositoryService
    {
        private readonly IXIdentityProvider identityProvider;
        private readonly IXAiMessageRepository messageRepository;
        private readonly IXAiConversationRepository conversationRepository;
        private readonly IXAiProjectTitleResourceProvider projectTitleResourceProvider;
        private readonly IXAiConversationTitleResourceProvider conversationTitleResourceProvider;
        private readonly IXAiProjectDescriptionResourceProvider projectDescriptionResourceProvider;

        public XAiProjectRepositoryService(
            IXAiProjectRepository repository,
            IXIdentityProvider identityProvider,
            IXAiMessageRepository messageRepository,
            IXAiConversationRepository conversationRepository,
            IXAiProjectTitleResourceProvider projectTitleResourceProvider,
            IXAiConversationTitleResourceProvider conversationTitleResourceProvider,
            IXAiProjectDescriptionResourceProvider projectDescriptionResourceProvider
        ) : base(
            repository,
            mapperProfiles:
            [
                new XAiModelMappersProfile()
            ]
        )
        {
            //
            this.identityProvider = identityProvider;
            this.messageRepository = messageRepository;
            this.conversationRepository = conversationRepository;
            this.projectTitleResourceProvider = projectTitleResourceProvider;
            this.conversationTitleResourceProvider = conversationTitleResourceProvider;
            this.projectDescriptionResourceProvider = projectDescriptionResourceProvider;
        }

        //
        #region Enrichers ...
        /// <summary>
        /// Enrich Specified Item ...
        /// </summary>
        /// <param name="source"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<XAiProjectDto> Enrich(
            XAiProject source,
            bool fillConversations = false,
            bool fillConversationMessages = false,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Mapping Default ...
            var result = Mapper.Map<XAiProjectDto>(source);
            result = await Enrich(
                source: result,
                fillConversations: fillConversations,
                cancellationToken: cancellationToken,
                fillConversationMessages: fillConversationMessages                
            );

            //
            return result;
        }

        /// <summary>
        /// Enrich Specified Item ...
        /// </summary>
        /// <param name="source"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<XAiProjectDto> Enrich(
            XAiProjectDto source,
            bool fillConversations = false,
            bool fillConversationMessages = false,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Handle Owner ...
            XPersonDto owner = null;
            if (!source.OwnerId.IsNullOrEmpty())
            {
                //
                // Try to Retrieve User Info ...
                var device = identityProvider.GetDevice();
                var userInfo = await identityProvider.GetUserInfo(
                    device: device,
                    userSelectByParam: source.OwnerId
                );
                owner = userInfo.ToXPersonDto();
            }

            //
            // Handle Title ...            
            XResourceDto titleResource = null;
            if (!source.Title.IsNullOrEmpty())
            {
                //
                titleResource = await projectTitleResourceProvider.GetResource(
                    owner: source.OwnerId,
                    identifier: source.Id,
                    cancellationToken: cancellationToken
                );
            }

            //
            // Handle Description ...
            XResourceDto descriptionResource = null;
            if (!source.Description.IsNullOrEmpty())
            {
                //
                descriptionResource = await projectDescriptionResourceProvider.GetResource(
                    owner: source.OwnerId,
                    identifier: source.Id,
                    cancellationToken: cancellationToken
                );
            }

            //
            // Handle Conversations ...
            IEnumerable<XAiConversationDto> conversations = [];
            if (fillConversations)
            {
                //
                // Reading Entities ...
                var entitiies = await conversationRepository.FindManyAsync(
                    includeBuilder: null,
                    ignoreSoftDeleteds: true,
                    cancellationToken: cancellationToken,
                    predicate: x => x.ProjectId == source.Id,
                    orderBuilder: x => x.OrderBy(y => y.CreatedOn)
                );

                //
                // Converts to Dto Enumerable ...
                conversations = entitiies
                    .MapConvert<XAiConversationDto, XAiConversation>();

                //
                // Handle Conversation Enrichment ...
                foreach (var conversation in conversations)
                {
                    //
                    // Handle Owner ...
                    if (!conversation.OwnerId.IsNullOrEmpty())
                    {
                        conversation.Owner = owner;
                    }

                    //
                    // Handle Title Resource ...
                    if (!conversation.Title.IsNullOrEmpty())
                    {
                        //
                        conversation.TitleResource = await conversationTitleResourceProvider.GetResource(
                            owner: source.OwnerId,
                            identifier: conversation.Id,
                            cancellationToken: cancellationToken
                        );
                    }

                    //
                    // Handle Project Info ...
                    if (!conversation.ProjectId.IsDefaultGuid())
                    {
                        conversation.ProjectTitle = source.Title;
                        conversation.ProjectTitleResource = titleResource;
                        conversation.ProjectDescription = source.Description;
                        conversation.ProjectDescriptionResource = descriptionResource;
                    }
                }

                //
                // Handle Fill Messages ...
                if (fillConversationMessages)
                {
                    //
                    foreach (var conversation in conversations)
                    {
                        //
                        IEnumerable<XAiMessageDto> messages = [];

                        //
                        // Retrieve Message Entities ...
                        var messageEntities = await messageRepository.FindManyAsync(
                            includeBuilder: null,
                            ignoreSoftDeleteds: true,
                            cancellationToken: cancellationToken,
                            orderBuilder: x => x.OrderBy(y => y.CreatedOn),
                            predicate: x => x.ConversationId == conversation.Id
                        );
                        if (messageEntities.HasChild())
                        {
                            //
                            // Converts to Message ...
                            messages = messageEntities
                                .MapConvert<XAiMessageDto, XAiMessage>();

                            //
                            // Ennrich Messages ...
                            foreach (var message in messages)
                            {
                                //
                                // Handle Owner ...
                                if (!message.OwnerId.IsNullOrEmpty())
                                {
                                    message.Owner = owner;
                                }

                                //
                                // Handle Conversation ...
                                if (!message.ConversationId.IsDefaultGuid())
                                {
                                    message.ConversationTitle = conversation.Title;
                                    message.ConversationTitleResouce = conversation.TitleResource;
                                }
                            }
                        }

                        //
                        conversation.Messages = [.. messages];
                    }
                }
            }

            //
            // Attach Extracted Items ...
            var result = Mapper.Map<XAiProjectDto>(source, options =>
            {
                //
                options.Items[nameof(source.Owner)] = owner;
                options.Items[nameof(source.Conversations)] = conversations;
                options.Items[nameof(source.TitleResource)] = titleResource;
                options.Items[nameof(source.DescriptionResource)] = descriptionResource;
            });

            //
            return result;
        }

        /// <summary>
        /// Enrich Specified Items ...
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<IEnumerable<XAiProjectDto>> EnrichRange(
            IEnumerable<XAiProject> sources,
            bool fillConversations = false,
            bool fillConversationMessages = false,
            CancellationToken cancellationToken = default
        )
        {
            //
            var tasks = sources
                .Select(x => Enrich(
                    source: x,
                    cancellationToken: cancellationToken,
                    fillConversations: fillConversations,
                    fillConversationMessages: fillConversationMessages
                ));

            //
            var result = await Task.WhenAll(tasks);
            return result;
        }

        /// <summary>
        /// Enrich Specified Items ...
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<IEnumerable<XAiProjectDto>> EnrichRange(
            IEnumerable<XAiProjectDto> sources,
            bool fillConversations = false,
            bool fillConversationMessages = false,
            CancellationToken cancellationToken = default
        )
        {
            //
            var tasks = sources
                .Select(x => Enrich(
                    source: x,
                    cancellationToken: cancellationToken,
                    fillConversations: fillConversations,
                    fillConversationMessages: fillConversationMessages
                ));

            //
            var result = await Task.WhenAll(tasks);
            return result;
        }
        #endregion        
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Dtos\XAiProjectServiceProvider.cs

using System;
using xPushService.Base;
using xAiModels.Models.Dtos;
using xAiModels.Interfaces.Dtos;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xIdentityService.Interfaces;
using Microsoft.AspNetCore.SignalR;

namespace xAiModels.Providers.Dtos
{
    public class XAiProjectServiceProvider : XBaseDtoProvider<XAiProject, XAiProjectDto, Guid, XAiProjectDtoHub>, IXAiProjectServiceProvider
    {
        public XAiProjectServiceProvider(
            IHubContext<XAiProjectDtoHub> hub,
            XDataServiceConfiguration dataConfiguration,
            IXAiProjectRepositoryService service,
            IXIdentityProvider identityProvider = null
        ) : base(
            hub,
            dataConfiguration,
            service,
            identityProvider
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Enrichers\XAiConversationEnricher.cs

using System.Linq;
using xModels.Dtos;
using System.Threading;
using xCommons.Extensions;
using xAiModels.Interfaces;
using xAiModels.Models.Dtos;
using System.Threading.Tasks;
using xAiModels.Models.Entities;
using System.Collections.Generic;
using xIdentityService.Interfaces;
using xIdentityService.Extensions;
using xAiModels.Interfaces.Entities;
using xAiModels.Interfaces.Enrichers;

namespace xAiModels.Providers.Enrichers
{
    public class XAiConversationEnricher : IXAiConversationEnricher
    {
        private readonly IXIdentityProvider identityProvider;
        private readonly IXAiConversationRepository repository;
        private readonly IXAiProjectTitleResourceProvider projectTitleResourceProvider;
        private readonly IXAiConversationTitleResourceProvider conversationTitleResourceProvider;
        private readonly IXAiProjectDescriptionResourceProvider projectDescriptionResourceProvider;

        public XAiConversationEnricher(
            IXIdentityProvider identityProvider,
            IXAiConversationRepository repository,
            IXAiProjectTitleResourceProvider projectTitleResourceProvider,
            IXAiConversationTitleResourceProvider conversationTitleResourceProvider,
            IXAiProjectDescriptionResourceProvider projectDescriptionResourceProvider
        )
        {
            //
            this.repository = repository;
            this.identityProvider = identityProvider;
            this.projectTitleResourceProvider = projectTitleResourceProvider;
            this.conversationTitleResourceProvider = conversationTitleResourceProvider;
            this.projectDescriptionResourceProvider = projectDescriptionResourceProvider;
        }

        //
        #region Enrichers ...
        /// <summary>
        /// Enrich Specified Item ...
        /// </summary>
        /// <param name="source"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<XAiConversationDto> Enrich(
            XAiConversation source,
            bool fillMessages = false,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Mapping Default ...
            var result = source.MapConvert<XAiConversationDto, XAiConversation>();
            result = await Enrich(
                source: result,
                fillMessages: fillMessages,
                cancellationToken: cancellationToken
            );

            //
            return result;
        }

        /// <summary>
        /// Enrich Specified Item ...
        /// </summary>
        /// <param name="source"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<XAiConversationDto> Enrich(
            XAiConversationDto source,
            bool fillMessages = false,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Handdle Owner ...
            XPersonDto owner = null;
            if (!source.OwnerId.IsNullOrEmpty())
            {
                //
                // Try to Retrieve User Info ...
                var device = identityProvider.GetDevice();
                var userInfo = await identityProvider.GetUserInfo(
                    device: device,
                    userSelectByParam: source.OwnerId
                );
                owner = userInfo.ToXPersonDto();
            }

            //
            // Handle Title Resource ...
            XResourceDto titleResource = null;
            if (!source.Title.IsNullOrEmpty())
            {
                //
                titleResource = await conversationTitleResourceProvider.GetResource(
                    owner: source.OwnerId,
                    identifier: source.Id,
                    cancellationToken: cancellationToken
                );
            }

            //
            // Handle Project Info ...
            XResourceDto projectTitleResource = null;
            XResourceDto projectDescriptionResource = null;
            if (!source.ProjectId.IsDefaultGuid())
            {
                //
                projectTitleResource = await projectTitleResourceProvider.GetResource(
                    owner: source.OwnerId,
                    identifier: source.ProjectId,
                    cancellationToken: cancellationToken
                );

                //
                projectDescriptionResource = await projectDescriptionResourceProvider.GetResource(
                    owner: source.OwnerId,
                    identifier: source.ProjectId,
                    cancellationToken: cancellationToken
                );
            }

            //
            // Attach Extracted Items ...
            var result = source.MapConvert<XAiConversationDto, XAiConversationDto>();
            result.Owner = owner;
            result.TitleResource = titleResource;
            result.ProjectTitleResource = projectTitleResource;
            result.ProjectDescriptionResource = projectDescriptionResource;

            //
            return result;
        }

        /// <summary>
        /// Enrich Specified Items ...
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<IEnumerable<XAiConversationDto>> EnrichRange(
            IEnumerable<XAiConversation> sources,
            bool fillMessages = false,
            CancellationToken cancellationToken = default
        )
        {
            //
            var tasks = sources
                .Select(x => Enrich(
                    source: x,
                    fillMessages: fillMessages,
                    cancellationToken: cancellationToken
                ));

            //
            var result = await Task.WhenAll(tasks);
            return result;
        }

        /// <summary>
        /// Enrich Specified Items ...
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="fillMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<IEnumerable<XAiConversationDto>> EnrichRange(
            IEnumerable<XAiConversationDto> sources,
            bool fillMessages = false,
            CancellationToken cancellationToken = default
        )
        {
            //
            var tasks = sources
                .Select(x => Enrich(
                    source: x,
                    fillMessages: fillMessages,
                    cancellationToken: cancellationToken
                ));

            //
            var result = await Task.WhenAll(tasks);
            return result;
        }
        #endregion                
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Enrichers\XAiMessageEnricher.cs

using System.Linq;
using xModels.Dtos;
using System.Threading;
using xCommons.Extensions;
using xAiModels.Interfaces;
using xAiModels.Models.Dtos;
using System.Threading.Tasks;
using xAiModels.Models.Entities;
using System.Collections.Generic;
using xIdentityService.Interfaces;
using xIdentityService.Extensions;
using xAiModels.Interfaces.Enrichers;

namespace xAiModels.Providers.Enrichers
{
    public class XAiMessageEnricher : IXAiMessageEnricher
    {
        private readonly IXIdentityProvider identityProvider;
        private readonly IXAiConversationTitleResourceProvider conversationTitleResourceProvider;

        public XAiMessageEnricher(
            IXIdentityProvider identityProvider,
            IXAiConversationTitleResourceProvider conversationTitleResourceProvider
        )
        {
            this.identityProvider = identityProvider;
            this.conversationTitleResourceProvider = conversationTitleResourceProvider;
        }

        //
        #region Enrichers ...
        /// <summary>
        /// Enrich Specified Item ...
        /// </summary>
        /// <param name="source"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<XAiMessageDto> Enrich(
            XAiMessage source,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Mapping Default ...
            var result = source.MapConvert<XAiMessageDto, XAiMessage>();
            result = await Enrich(
                source: result,
                cancellationToken: cancellationToken
            );

            //
            return result;
        }

        /// <summary>
        /// Enrich Specified Item ...
        /// </summary>
        /// <param name="source"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<XAiMessageDto> Enrich(
            XAiMessageDto source,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Handdle Owner ...
            XPersonDto owner = null;
            if (!source.OwnerId.IsNullOrEmpty())
            {
                //
                // Try to Retrieve User Info ...
                var device = identityProvider.GetDevice();
                var userInfo = await identityProvider.GetUserInfo(
                    device: device,
                    userSelectByParam: source.OwnerId
                );
                owner = userInfo.ToXPersonDto();
            }

            //
            // Hanlde Conversation Title ...
            string conversationTitle = string.Empty;
            XResourceDto conversationTitleResource = null;
            if (!source.ConversationId.IsDefaultGuid())
            {
                //
                conversationTitleResource = await conversationTitleResourceProvider.GetResource(
                    owner: source.OwnerId,
                    identifier: source.ConversationId,
                    cancellationToken: cancellationToken
                );
                if (!conversationTitleResource.IsNullOrDefault())
                {
                    conversationTitle = conversationTitleResource.Resource;
                }
            }

            //
            // Attach Extracted Items ...
            var result = source.MapConvert<XAiMessageDto, XAiMessageDto>();
            result.Owner = owner;
            result.ConversationTitle = conversationTitle;
            result.ConversationTitleResouce = conversationTitleResource;

            //
            return result;
        }

        /// <summary>
        /// Enrich Specified Items ...
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<IEnumerable<XAiMessageDto>> EnrichRange(
            IEnumerable<XAiMessage> sources,
            CancellationToken cancellationToken = default
        )
        {
            //
            var tasks = sources
                .Select(x => Enrich(x, cancellationToken));

            //
            var result = await Task.WhenAll(tasks);
            return result;
        }

        /// <summary>
        /// Enrich Specified Items ...
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<IEnumerable<XAiMessageDto>> EnrichRange(
            IEnumerable<XAiMessageDto> sources,
            CancellationToken cancellationToken = default
        )
        {
            //
            var tasks = sources
                .Select(x => Enrich(x, cancellationToken));

            //
            var result = await Task.WhenAll(tasks);
            return result;
        }
        #endregion        
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Enrichers\XAiProjectEnricher.cs

using System.Linq;
using xModels.Dtos;
using System.Threading;
using xCommons.Extensions;
using xAiModels.Interfaces;
using xAiModels.Models.Dtos;
using System.Threading.Tasks;
using xAiModels.Models.Entities;
using System.Collections.Generic;
using xIdentityService.Interfaces;
using xIdentityService.Extensions;
using xAiModels.Interfaces.Entities;
using xAiModels.Interfaces.Enrichers;

namespace xAiModels.Providers.Enrichers
{
    public class XAiProjectEnricher : IXAiProjectEnricher
    {
        private readonly IXIdentityProvider identityProvider;
        private readonly IXAiMessageRepository messageRepository;
        private readonly IXAiConversationRepository conversationRepository;
        private readonly IXAiProjectTitleResourceProvider projectTitleResourceProvider;
        private readonly IXAiConversationTitleResourceProvider conversationTitleResourceProvider;
        private readonly IXAiProjectDescriptionResourceProvider projectDescriptionResourceProvider;

        public XAiProjectEnricher(
            IXIdentityProvider identityProvider,
            IXAiMessageRepository messageRepository,
            IXAiConversationRepository conversationRepository,
            IXAiProjectTitleResourceProvider projectTitleResourceProvider,
            IXAiConversationTitleResourceProvider conversationTitleResourceProvider,
            IXAiProjectDescriptionResourceProvider projectDescriptionResourceProvider
        )
        {
            //
            this.identityProvider = identityProvider;
            this.messageRepository = messageRepository;
            this.conversationRepository = conversationRepository;
            this.projectTitleResourceProvider = projectTitleResourceProvider;
            this.conversationTitleResourceProvider = conversationTitleResourceProvider;
            this.projectDescriptionResourceProvider = projectDescriptionResourceProvider;
        }

        //
        #region Enrichers ...
        /// <summary>
        /// Enrich Specified Item ...
        /// </summary>
        /// <param name="source"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<XAiProjectDto> Enrich(
            XAiProject source,
            bool fillConversations = false,
            bool fillConversationMessages = false,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Mapping Default ...
            var result = source.MapConvert<XAiProjectDto, XAiProject>();
            result = await Enrich(
                source: result,
                fillConversations: fillConversations,
                cancellationToken: cancellationToken,
                fillConversationMessages: fillConversationMessages
            );

            //
            return result;
        }

        /// <summary>
        /// Enrich Specified Item ...
        /// </summary>
        /// <param name="source"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<XAiProjectDto> Enrich(
            XAiProjectDto source,
            bool fillConversations = false,
            bool fillConversationMessages = false,
            CancellationToken cancellationToken = default
        )
        {
            //
            // Handle Owner ...
            XPersonDto owner = null;
            if (!source.OwnerId.IsNullOrEmpty())
            {
                //
                // Try to Retrieve User Info ...
                var device = identityProvider.GetDevice();
                var userInfo = await identityProvider.GetUserInfo(
                    device: device,
                    userSelectByParam: source.OwnerId
                );
                owner = userInfo.ToXPersonDto();
            }

            //
            // Handle Title ...            
            XResourceDto titleResource = null;
            if (!source.Title.IsNullOrEmpty())
            {
                //
                titleResource = await projectTitleResourceProvider.GetResource(
                    owner: source.OwnerId,
                    identifier: source.Id,
                    cancellationToken: cancellationToken
                );
            }

            //
            // Handle Description ...
            XResourceDto descriptionResource = null;
            if (!source.Description.IsNullOrEmpty())
            {
                //
                descriptionResource = await projectDescriptionResourceProvider.GetResource(
                    owner: source.OwnerId,
                    identifier: source.Id,
                    cancellationToken: cancellationToken
                );
            }

            //
            // Handle Conversations ...
            IEnumerable<XAiConversationDto> conversations = [];
            if (fillConversations)
            {
                //
                // Reading Entities ...
                var entitiies = await conversationRepository.FindManyAsync(
                    includeBuilder: null,
                    ignoreSoftDeleteds: true,
                    cancellationToken: cancellationToken,
                    predicate: x => x.ProjectId == source.Id,
                    orderBuilder: x => x.OrderBy(y => y.CreatedOn)
                );

                //
                // Converts to Dto Enumerable ...
                conversations = entitiies
                    .MapConvert<XAiConversationDto, XAiConversation>();

                //
                // Handle Conversation Enrichment ...
                foreach (var conversation in conversations)
                {
                    //
                    // Handle Owner ...
                    if (!conversation.OwnerId.IsNullOrEmpty())
                    {
                        conversation.Owner = owner;
                    }

                    //
                    // Handle Title Resource ...
                    if (!conversation.Title.IsNullOrEmpty())
                    {
                        //
                        conversation.TitleResource = await conversationTitleResourceProvider.GetResource(
                            owner: source.OwnerId,
                            identifier: conversation.Id,
                            cancellationToken: cancellationToken
                        );
                    }

                    //
                    // Handle Project Info ...
                    if (!conversation.ProjectId.IsDefaultGuid())
                    {
                        conversation.ProjectTitle = source.Title;
                        conversation.ProjectTitleResource = titleResource;
                        conversation.ProjectDescription = source.Description;
                        conversation.ProjectDescriptionResource = descriptionResource;
                    }
                }

                //
                // Handle Fill Messages ...
                if (fillConversationMessages)
                {
                    //
                    foreach (var conversation in conversations)
                    {
                        //
                        IEnumerable<XAiMessageDto> messages = [];

                        //
                        // Retrieve Message Entities ...
                        var messageEntities = await messageRepository.FindManyAsync(
                            includeBuilder: null,
                            ignoreSoftDeleteds: true,
                            cancellationToken: cancellationToken,
                            orderBuilder: x => x.OrderBy(y => y.CreatedOn),
                            predicate: x => x.ConversationId == conversation.Id
                        );
                        if (messageEntities.HasChild())
                        {
                            //
                            // Converts to Message ...
                            messages = messageEntities
                                .MapConvert<XAiMessageDto, XAiMessage>();

                            //
                            // Ennrich Messages ...
                            foreach (var message in messages)
                            {
                                //
                                // Handle Owner ...
                                if (!message.OwnerId.IsNullOrEmpty())
                                {
                                    message.Owner = owner;
                                }

                                //
                                // Handle Conversation ...
                                if (!message.ConversationId.IsDefaultGuid())
                                {
                                    message.ConversationTitle = conversation.Title;
                                    message.ConversationTitleResouce = conversation.TitleResource;
                                }
                            }
                        }

                        //
                        conversation.Messages = [.. messages];
                    }
                }
            }

            //
            // Attach Extracted Items ...
            var result = source.MapConvert<XAiProjectDto, XAiProjectDto>();
            result.Owner = owner;
            result.TitleResource = titleResource;
            result.Conversations = [.. conversations];
            result.DescriptionResource = descriptionResource;

            //
            return result;
        }

        /// <summary>
        /// Enrich Specified Items ...
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<IEnumerable<XAiProjectDto>> EnrichRange(
            IEnumerable<XAiProject> sources,
            bool fillConversations = false,
            bool fillConversationMessages = false,
            CancellationToken cancellationToken = default
        )
        {
            //
            var tasks = sources
                .Select(x => Enrich(
                    source: x,
                    cancellationToken: cancellationToken,
                    fillConversations: fillConversations,
                    fillConversationMessages: fillConversationMessages
                ));

            //
            var result = await Task.WhenAll(tasks);
            return result;
        }

        /// <summary>
        /// Enrich Specified Items ...
        /// </summary>
        /// <param name="sources"></param>
        /// <param name="fillConversations"></param>
        /// <param name="fillConversationMessages"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public virtual async Task<IEnumerable<XAiProjectDto>> EnrichRange(
            IEnumerable<XAiProjectDto> sources,
            bool fillConversations = false,
            bool fillConversationMessages = false,
            CancellationToken cancellationToken = default
        )
        {
            //
            var tasks = sources
                .Select(x => Enrich(
                    source: x,
                    cancellationToken: cancellationToken,
                    fillConversations: fillConversations,
                    fillConversationMessages: fillConversationMessages
                ));

            //
            var result = await Task.WhenAll(tasks);
            return result;
        }
        #endregion        
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Entities\XAiConversationEFRepository.cs

using System;
using xDataService.Db;
using xDataService.Providers;
using xDataService.Interfaces;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.Entities
{
    public class XAiConversationEFRepository<TContext> : XBaseEFRepository<XAiConversation, Guid, TContext>, IXAiConversationRepository
    where TContext : XDbContext
    {
        public XAiConversationEFRepository(
            IXUnitOfWorks<TContext> unitOfWorks,
            XDataServiceConfiguration configuration,
            IXAiConversationKeyGenerator keyGenerator = null,
            IXAiConversationRepositoryEvents baseRepositoryEvents = null
        ) : base(
            unitOfWorks,
            configuration,
            keyGenerator,
            baseRepositoryEvents
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Entities\XAiConversationEntityHub.cs

using System;
using xPushService.Base;
using xAiModels.Models.Entities;
using Microsoft.Extensions.Logging;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.Entities
{
    public class XAiConversationEntityHub : XBaseEntityHub<XAiConversation, Guid>, IXAiConversationEntityHub
    {
        public XAiConversationEntityHub(
            ILogger<XAiConversationEntityHub> logger
        ) : base(logger)
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Entities\XAiConversationInMemoryRepository.cs

using System;
using xDataService.Providers;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.Entities
{
    public class XAiConversationInMemoryRepository : XBaseInMemoryRepository<XAiConversation, Guid>, IXAiConversationRepository
    {
        public XAiConversationInMemoryRepository(
            XDataServiceConfiguration configuration,
            IXAiConversationKeyGenerator keyGenerator = null,
            IXAiConversationRepositoryEvents baseRepositoryEvents = null
        ) : base(
            configuration,
            keyGenerator,
            baseRepositoryEvents
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Entities\XAiConversationKeyGenerator.cs

using xDataService.Providers;
using xAiModels.Models.Entities;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.Entities
{
    public class XAiConversationKeyGenerator : XGuidKeyGenerator<XAiConversation>, IXAiConversationKeyGenerator
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Entities\XAiConversationMongoRepository.cs

using System;
using xDataService.Providers;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.Entities
{
    public class XAiConversationMongoRepository : XBaseMongoRepository<XAiConversation, Guid>, IXAiConversationRepository
    {
        public XAiConversationMongoRepository(
            XDataBaseConfiguration dbConfiguration,
            XDataServiceConfiguration configuration,
            string collectionName = null,
            IXAiConversationKeyGenerator keyGenerator = null,
            IXAiConversationRepositoryEvents baseRepositoryEvents = null
        ) : base(
            dbConfiguration,
            configuration,
            collectionName,
            keyGenerator,
            baseRepositoryEvents
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Entities\XAiConversationRepositoryEvents.cs

using xDataService.Providers;
using xAiModels.Models.Entities;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.Entities
{
    public class XAiConversationRepositoryEvents : XBaseRepositoryEvents<XAiConversation>, IXAiConversationRepositoryEvents
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Entities\XAiConversationRepositoryProvider.cs

using System;
using xPushService.Base;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xIdentityService.Interfaces;
using Microsoft.AspNetCore.SignalR;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.Entities
{
    public class XAiConversationRepositoryProvider : XBaseEntityProvider<XAiConversation, Guid, XAiConversationEntityHub>, IXAiConversationRepositoryProvider
    {
        public XAiConversationRepositoryProvider(
            IHubContext<XAiConversationEntityHub> hub,
            IXAiConversationRepository repository,
            XDataServiceConfiguration dataConfiguration,
            IXIdentityProvider identityProvider = null
        ) : base(
            hub,
            repository,
            dataConfiguration,
            identityProvider
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Entities\XAiConversationSeederBase.cs

using System;
using xDataService.Providers;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.Entities
{
    public abstract class XAiConversationSeederBase : XBaseDbSeeder<XAiConversation, Guid>, IXAiConversationSeeder
    {
        protected XAiConversationSeederBase(
            string entity,
            string database,
            IXAiConversationRepository repository,
            XDataServiceConfiguration dataServiceConfiguration
        ) : base(
            entity,
            database,
            repository,
            dataServiceConfiguration
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Entities\XAiMessageEFRepository.cs

using System;
using xDataService.Db;
using xDataService.Providers;
using xDataService.Interfaces;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.Entities
{
    public class XAiMessageEFRepository<TContext> : XBaseEFRepository<XAiMessage, Guid, TContext>, IXAiMessageRepository
    where TContext : XDbContext
    {
        public XAiMessageEFRepository(
            IXUnitOfWorks<TContext> unitOfWorks,
            XDataServiceConfiguration configuration,
            IXAiMessageKeyGenerator keyGenerator = null,
            IXAiMessageRepositoryEvents baseRepositoryEvents = null
        ) : base(
            unitOfWorks,
            configuration,
            keyGenerator,
            baseRepositoryEvents
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Entities\XAiMessageEntityHub.cs

using System;
using xPushService.Base;
using xAiModels.Models.Entities;
using Microsoft.Extensions.Logging;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.Entities
{
    public class XAiMessageEntityHub : XBaseEntityHub<XAiMessage, Guid>, IXAiMessageEntityHub
    {
        public XAiMessageEntityHub(
            ILogger<XAiMessageEntityHub> logger
        ) : base(logger)
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Entities\XAiMessageInMemoryRepository.cs

using System;
using xDataService.Providers;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.Entities
{
    public class XAiMessageInMemoryRepository : XBaseInMemoryRepository<XAiMessage, Guid>, IXAiMessageRepository
    {
        public XAiMessageInMemoryRepository(
            XDataServiceConfiguration configuration,
            IXAiMessageKeyGenerator keyGenerator = null,
            IXAiMessageRepositoryEvents baseRepositoryEvents = null
        ) : base(
            configuration,
            keyGenerator,
            baseRepositoryEvents
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Entities\XAiMessageKeyGenerator.cs

using xDataService.Providers;
using xAiModels.Models.Entities;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.Entities
{
    public class XAiMessageKeyGenerator : XGuidKeyGenerator<XAiMessage>, IXAiMessageKeyGenerator
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Entities\XAiMessageMongoRepository.cs

using System;
using xDataService.Providers;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.Entities
{
    public class XAiMessageMongoRepository : XBaseMongoRepository<XAiMessage, Guid>, IXAiMessageRepository
    {
        public XAiMessageMongoRepository(
            XDataBaseConfiguration dbConfiguration,
            XDataServiceConfiguration configuration,
            string collectionName = null,
            IXAiMessageKeyGenerator keyGenerator = null,
            IXAiMessageRepositoryEvents baseRepositoryEvents = null
        ) : base(
            dbConfiguration,
            configuration,
            collectionName,
            keyGenerator,
            baseRepositoryEvents
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Entities\XAiMessageRepositoryEvents.cs

using xDataService.Providers;
using xAiModels.Models.Entities;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.Entities
{
    public class XAiMessageRepositoryEvents : XBaseRepositoryEvents<XAiMessage>, IXAiMessageRepositoryEvents
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Entities\XAiMessageRepositoryProvider.cs

using System;
using xPushService.Base;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xIdentityService.Interfaces;
using Microsoft.AspNetCore.SignalR;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.Entities
{
    public class XAiMessageRepositoryProvider : XBaseEntityProvider<XAiMessage, Guid, XAiMessageEntityHub>, IXAiMessageRepositoryProvider
    {
        public XAiMessageRepositoryProvider(
            IHubContext<XAiMessageEntityHub> hub,
            IXAiMessageRepository repository,
            XDataServiceConfiguration dataConfiguration,
            IXIdentityProvider identityProvider = null
        ) : base(
            hub,
            repository,
            dataConfiguration,
            identityProvider
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Entities\XAiMessageSeederBase.cs

using System;
using xDataService.Providers;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.Entities
{
    public abstract class XAiMessageSeederBase : XBaseDbSeeder<XAiMessage, Guid>, IXAiMessageSeeder
    {
        protected XAiMessageSeederBase(
            string entity,
            string database,
            IXAiMessageRepository repository,
            XDataServiceConfiguration dataServiceConfiguration
        ) : base(
            entity,
            database,
            repository,
            dataServiceConfiguration
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Entities\XAiProjectEFRepository.cs

using System;
using xDataService.Db;
using xDataService.Providers;
using xDataService.Interfaces;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.Entities
{
    public class XAiProjectEFRepository<TContext> : XBaseEFRepository<XAiProject, Guid, TContext>, IXAiProjectRepository
    where TContext : XDbContext
    {
        public XAiProjectEFRepository(
            IXUnitOfWorks<TContext> unitOfWorks,
            XDataServiceConfiguration configuration,
            IXAiProjectKeyGenerator keyGenerator = null,
            IXAiProjectRepositoryEvents baseRepositoryEvents = null
        ) : base(
            unitOfWorks,
            configuration,
            keyGenerator,
            baseRepositoryEvents
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Entities\XAiProjectEntityHub.cs

using System;
using xPushService.Base;
using xAiModels.Models.Entities;
using Microsoft.Extensions.Logging;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.Entities
{
    public class XAiProjectEntityHub : XBaseEntityHub<XAiProject, Guid>, IXAiProjectEntityHub
    {
        public XAiProjectEntityHub(
            ILogger<XAiProjectEntityHub> logger
        ) : base(logger)
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Entities\XAiProjectInMemoryRepository.cs

using System;
using xDataService.Providers;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.Entities
{
    public class XAiProjectInMemoryRepository : XBaseInMemoryRepository<XAiProject, Guid>, IXAiProjectRepository
    {
        public XAiProjectInMemoryRepository(
            XDataServiceConfiguration configuration,
            IXAiProjectKeyGenerator keyGenerator = null,
            IXAiProjectRepositoryEvents baseRepositoryEvents = null
        ) : base(
            configuration,
            keyGenerator,
            baseRepositoryEvents
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Entities\XAiProjectKeyGenerator.cs

using xDataService.Providers;
using xAiModels.Models.Entities;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.Entities
{
    public class XAiProjectKeyGenerator : XGuidKeyGenerator<XAiProject>, IXAiProjectKeyGenerator
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Entities\XAiProjectMongoRepository.cs

using System;
using xDataService.Providers;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.Entities
{
    public class XAiProjectMongoRepository : XBaseMongoRepository<XAiProject, Guid>, IXAiProjectRepository
    {
        public XAiProjectMongoRepository(
            XDataBaseConfiguration dbConfiguration,
            XDataServiceConfiguration configuration,
            string collectionName = null,
            IXAiProjectKeyGenerator keyGenerator = null,
            IXAiProjectRepositoryEvents baseRepositoryEvents = null
        ) : base(
            dbConfiguration,
            configuration,
            collectionName,
            keyGenerator,
            baseRepositoryEvents
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Entities\XAiProjectRepositoryEvents.cs

using xDataService.Providers;
using xAiModels.Models.Entities;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.Entities
{
    public class XAiProjectRepositoryEvents : XBaseRepositoryEvents<XAiProject>, IXAiProjectRepositoryEvents
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Entities\XAiProjectRepositoryProvider.cs

using System;
using xPushService.Base;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xIdentityService.Interfaces;
using Microsoft.AspNetCore.SignalR;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.Entities
{
    public class XAiProjectRepositoryProvider : XBaseEntityProvider<XAiProject, Guid, XAiProjectEntityHub>, IXAiProjectRepositoryProvider
    {
        public XAiProjectRepositoryProvider(
            IHubContext<XAiProjectEntityHub> hub,
            IXAiProjectRepository repository,
            XDataServiceConfiguration dataConfiguration,
            IXIdentityProvider identityProvider = null
        ) : base(
            hub,
            repository,
            dataConfiguration,
            identityProvider
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Entities\XAiProjectSeederBase.cs

using System;
using xDataService.Providers;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.Entities
{
    public abstract class XAiProjectSeederBase : XBaseDbSeeder<XAiProject, Guid>, IXAiProjectSeeder
    {
        protected XAiProjectSeederBase(
            string entity,
            string database,
            IXAiProjectRepository repository,
            XDataServiceConfiguration dataServiceConfiguration
        ) : base(
            entity,
            database,
            repository,
            dataServiceConfiguration
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\GraphQL\XAiConversationGraphQLTypeHelper.cs

using System;
using xAiModels.Constants;
using xDataService.GraphQL;
using xAiModels.Models.Entities;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.GraphQL
{
    public class XAiConversationGraphQLTypeHelper : XBaseGraphQLTypeHelper<XAiConversation, Guid>, IXAiConversationGraphQLTypeHelper
    {
        public override string GetInQueryCollectionName()
        {
            return XAiModelsConstants.XAiConversationCollectionName;
        }

        public override string GetInQuerySingleName()
        {
            return XAiModelsConstants.XAiConversationSignleName;
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\GraphQL\XAiConversationGraphQuery.cs

using System;
using GraphQL.Types;
using xDataService.GraphQL;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.GraphQL
{
    public class XAiConversationGraphQuery : XBaseGraphQLQuery<XAiConversation, Guid, XAiConversationGraphType, GuidGraphType>
    {
        public XAiConversationGraphQuery(
            XDataServiceConfiguration configuration,
            IXAiConversationRepository repository,
            IXAiConversationGraphQLTypeHelper helper
        ) : base(
            configuration,
            repository,
            helper
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\GraphQL\XAiConversationGraphSchema.cs

using System;
using GraphQL.Types;

namespace xAiModels.Providers.GraphQL
{
    public class XAiConversationGraphSchema : Schema
    {
        public XAiConversationGraphSchema(IServiceProvider services) : base(services)
        {
            Query = (XAiConversationGraphQuery)services.GetService(typeof(XAiConversationGraphQuery));
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\GraphQL\XAiConversationGraphType.cs

using System;
using xDataService.GraphQL;
using xAiModels.Models.Entities;

namespace xAiModels.Providers.GraphQL
{
    public class XAiConversationGraphType : XBaseGraphObjectType<XAiConversation, Guid>
    {
        public XAiConversationGraphType() : base()
        {
            Field(x => x.Title);
            Field(x => x.OwnerId);
            Field(x => x.CreatedOn);
            Field(x => x.UpdatedAt);
            Field(x => x.ProjectId);
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\GraphQL\XAiMessageGraphQLTypeHelper.cs

using System;
using xAiModels.Constants;
using xDataService.GraphQL;
using xAiModels.Models.Entities;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.GraphQL
{
    public class XAiMessageGraphQLTypeHelper : XBaseGraphQLTypeHelper<XAiMessage, Guid>, IXAiMessageGraphQLTypeHelper
    {
        public override string GetInQueryCollectionName()
        {
            return XAiModelsConstants.XAiMessageCollectionName;
        }

        public override string GetInQuerySingleName()
        {
            return XAiModelsConstants.XAiMessageSingleName;
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\GraphQL\XAiMessageGraphQuery.cs

using System;
using GraphQL.Types;
using xDataService.GraphQL;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.GraphQL
{
    public class XAiMessageGraphQuery : XBaseGraphQLQuery<XAiMessage, Guid, XAiMessageGraphType, GuidGraphType>
    {
        public XAiMessageGraphQuery(
            XDataServiceConfiguration configuration,
            IXAiMessageRepository repository,
            IXAiMessageGraphQLTypeHelper helper
        ) : base(
            configuration,
            repository,
            helper
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\GraphQL\XAiMessageGraphSchema.cs

using System;
using GraphQL.Types;

namespace xAiModels.Providers.GraphQL
{
    public class XAiMessageGraphSchema : Schema
    {
        public XAiMessageGraphSchema(IServiceProvider services) : base(services)
        {
            Query = (XAiMessageGraphQuery)services.GetService(typeof(XAiMessageGraphQuery));
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\GraphQL\XAiMessageGraphType.cs

using System;
using xDataService.GraphQL;
using xAiModels.Models.Entities;

namespace xAiModels.Providers.GraphQL
{
    public class XAiMessageGraphType: XBaseGraphObjectType<XAiMessage, Guid>
    {
        public XAiMessageGraphType() : base()
        {
            Field(x => x.Role);
            Field(x => x.OwnerId);
            Field(x => x.Content);
            Field(x => x.Metadata);
            Field(x => x.CreatedOn);
            Field(x => x.UpdatedAt);
            Field(x => x.ConversationId);
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\GraphQL\XAiProjectGraphQLTypeHelper.cs

using System;
using xAiModels.Constants;
using xDataService.GraphQL;
using xAiModels.Models.Entities;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.GraphQL
{
    public class XAiProjectGraphQLTypeHelper : XBaseGraphQLTypeHelper<XAiProject, Guid>, IXAiProjectGraphQLTypeHelper
    {
        public override string GetInQueryCollectionName()
        {
            return XAiModelsConstants.XAiProjectCollectionName;
        }

        public override string GetInQuerySingleName()
        {
            return XAiModelsConstants.XAiProjectSingleName;
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\GraphQL\XAiProjectGraphQuery.cs

using System;
using GraphQL.Types;
using xDataService.GraphQL;
using xAiModels.Models.Entities;
using xDataService.Configuration;
using xAiModels.Interfaces.Entities;

namespace xAiModels.Providers.GraphQL
{
    public class XAiProjectGraphQuery : XBaseGraphQLQuery<XAiProject, Guid, XAiProjectGraphType, GuidGraphType>
    {
        public XAiProjectGraphQuery(
            XDataServiceConfiguration configuration,
            IXAiProjectRepository repository,
            IXAiProjectGraphQLTypeHelper helper
        ) : base(
            configuration,
            repository,
            helper
        )
        { }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\GraphQL\XAiProjectGraphSchema.cs

using System;
using GraphQL.Types;

namespace xAiModels.Providers.GraphQL
{
    public class XAiProjectGraphSchema : Schema
    {
        public XAiProjectGraphSchema(IServiceProvider services) : base(services)
        {
            Query = (XAiProjectGraphQuery)services.GetService(typeof(XAiProjectGraphQuery));
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\GraphQL\XAiProjectGraphType.cs

using System;
using xDataService.GraphQL;
using xAiModels.Models.Entities;

namespace xAiModels.Providers.GraphQL
{
    public class XAiProjectGraphType : XBaseGraphObjectType<XAiProject, Guid>
    {
        public XAiProjectGraphType() : base()
        {
            Field(x => x.Title);
            Field(x => x.OwnerId);
            Field(x => x.Prompt);
            Field(x => x.CreatedOn);
            Field(x => x.UpdatedAt);
            Field(x => x.Description);
        }
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiModels\Providers\Hubs\XAiDataProviderHub.cs

using System.Threading;
using xPushService.Base;
using xAiModels.Constants;
using xCommons.Extensions;
using xAiModels.Models.Dtos;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;

namespace xAiModels.Providers.Hubs
{
    public class XAiDataProviderHub : XBaseHub
    {
        public XAiDataProviderHub(
            ILogger<XBaseHub> logger
        ) : base(logger)
        { }

        //
        #region XAiProject ...
        /// <summary>
        /// Notify a New Item Created ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task ProjectCreated(
            XAiProjectDto item,
            CancellationToken cancellationToken = default
        )
        {
            //
            var connectionId = Context.ConnectionId;
            if (!connectionId.IsNullOrEmpty())
            {
                //
                await Clients
                    .AllExcept(connectionId)
                    .SendAsync(
                        XAiDataProviderHubActions.ProjectCreated.GetStringValue(),
                        item.ToJSON(camelCase: true),
                        connectionId,
                        cancellationToken
                    );
            }
        }

        /// <summary>
        /// Notify an Item Removed ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task ProjectRemoved(
            XAiProjectDto item,
            CancellationToken cancellationToken = default
        )
        {
            //
            var connectionId = Context.ConnectionId;
            if (!connectionId.IsNullOrEmpty())
            {
                //
                await Clients
                    .AllExcept(connectionId)
                    .SendAsync(
                        XAiDataProviderHubActions.ProjectRemoved.GetStringValue(),
                        item.ToJSON(camelCase: true),
                        connectionId,
                        cancellationToken
                    );
            }
        }

        /// <summary>
        /// Notify an Item Updated ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task ProjectUpdated(
            XAiProjectDto item,
            CancellationToken cancellationToken = default
        )
        {
            //
            var connectionId = Context.ConnectionId;
            if (!connectionId.IsNullOrEmpty())
            {
                //
                await Clients
                    .AllExcept(connectionId)
                    .SendAsync(
                        XAiDataProviderHubActions.ProjectUpdated.GetStringValue(),
                        item.ToJSON(camelCase: true),
                        connectionId,
                        cancellationToken
                    );
            }
        }
        #endregion

        //
        #region XAiConversation ...
        /// <summary>
        /// Notify a New Item Created ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task ConversationCreated(
            XAiProjectDto item,
            CancellationToken cancellationToken = default
        )
        {
            //
            var connectionId = Context.ConnectionId;
            if (!connectionId.IsNullOrEmpty())
            {
                //
                await Clients
                    .AllExcept(connectionId)
                    .SendAsync(
                        XAiDataProviderHubActions.ConversationCreated.GetStringValue(),
                        item.ToJSON(camelCase: true),
                        connectionId,
                        cancellationToken
                    );
            }
        }

        /// <summary>
        /// Notify an Item Removed ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task ConversationRemoved(
            XAiProjectDto item,
            CancellationToken cancellationToken = default
        )
        {
            //
            var connectionId = Context.ConnectionId;
            if (!connectionId.IsNullOrEmpty())
            {
                //
                await Clients
                    .AllExcept(connectionId)
                    .SendAsync(
                        XAiDataProviderHubActions.ConversationRemoved.GetStringValue(),
                        item.ToJSON(camelCase: true),
                        connectionId,
                        cancellationToken
                    );
            }
        }

        /// <summary>
        /// Notify an Item Updated ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task ConversationUpdated(
            XAiProjectDto item,
            CancellationToken cancellationToken = default
        )
        {
            //
            var connectionId = Context.ConnectionId;
            if (!connectionId.IsNullOrEmpty())
            {
                //
                await Clients
                    .AllExcept(connectionId)
                    .SendAsync(
                        XAiDataProviderHubActions.ConversationUpdated.GetStringValue(),
                        item.ToJSON(camelCase: true),
                        connectionId,
                        cancellationToken
                    );
            }
        }
        #endregion

        //
        #region XAiMessage ...
        /// <summary>
        /// Notify a New Item Created ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task MessageCreated(
            XAiProjectDto item,
            CancellationToken cancellationToken = default
        )
        {
            //
            var connectionId = Context.ConnectionId;
            if (!connectionId.IsNullOrEmpty())
            {
                //
                await Clients
                    .AllExcept(connectionId)
                    .SendAsync(
                        XAiDataProviderHubActions.MessageCreated.GetStringValue(),
                        item.ToJSON(camelCase: true),
                        connectionId,
                        cancellationToken
                    );
            }
        }

        /// <summary>
        /// Notify an Item Removed ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task MessageRemoved(
            XAiProjectDto item,
            CancellationToken cancellationToken = default
        )
        {
            //
            var connectionId = Context.ConnectionId;
            if (!connectionId.IsNullOrEmpty())
            {
                //
                await Clients
                    .AllExcept(connectionId)
                    .SendAsync(
                        XAiDataProviderHubActions.MessageRemoved.GetStringValue(),
                        item.ToJSON(camelCase: true),
                        connectionId,
                        cancellationToken
                    );
            }
        }

        /// <summary>
        /// Notify an Item Updated ...
        /// </summary>
        /// <param name="item"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task MessageUpdated(
            XAiProjectDto item,
            CancellationToken cancellationToken = default
        )
        {
            //
            var connectionId = Context.ConnectionId;
            if (!connectionId.IsNullOrEmpty())
            {
                //
                await Clients
                    .AllExcept(connectionId)
                    .SendAsync(
                        XAiDataProviderHubActions.MessageUpdated.GetStringValue(),
                        item.ToJSON(camelCase: true),
                        connectionId,
                        cancellationToken
                    );
            }
        }
        #endregion
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiService\Constants\ConfigurationNodeNames.cs

namespace xAiService.Constants
{
    public partial struct ConfigurationNodeNames
    {
        public const string AI_SERVICE_NODE = "AIServiceConfiguration";
    }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiService\DI\XDIHelperExtension.cs

namespace xAiService.DI
{
    public static partial class XDIHelperExtension
    { }
}

### FILE: C:\Users\hkhazaeeasl\Documents\Projets\XProjects\xSaherelmWorkspace\Modules\xAiService\Helpers\VectorHelper.cs

using System;
using xCommons.Extensions;

namespace xAiService.Helpers
{
    public static class VectorHelper
    {
        /// <summary>
        /// Normalize a Vector ...
        /// </summary>
        /// <param name="vector"></param>
        public static void Normalize(float[] vector)
        {
            //
            // Validate ...
            if (vector.IsNull() || !vector.HasChild())
            {
                return;
            }

            //
            float sum = 0f;
            foreach (var value in vector)
            {
                sum += value * value;
            }

            //
            float norm = MathF.Sqrt(sum);
            if (norm <= 1e-9f)
            {
                return;
            }

            //
            for (int i = 0; i < vector.Length; i++)
            {
                vector[i] /= norm;
            }
        }

        /// <summary>
        /// Similarity Check ...
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public static float CosineSimilarity(float[] a, float[] b)
        {
            //
            if (a.IsNull() || b.IsNull())
            {
                return 0f;
            }

            //
            if (a.Length != b.Length)
            {
                return 0f;
            }

            //
            float dot = 0f;
            float normA = 0f;
            float normB = 0f;
            for (int i = 0; i < a.Length; i++)
            {
                //
                dot += a[i] * b[i];
                normA += a[i] * a[i];
                normB += b[i] * b[i];
            }

            //
            float denominator = MathF.Sqrt(normA) * MathF.Sqrt(normB);
            if (denominator <= 1e-9f)
            {
                return 0f;
            }

            //
            var result = dot / denominator;
            return result;
        }
    }
}
