<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>GEDminer - GEDCOM Analysis Tool</title>

    <script src="https://cdn.tailwindcss.com"></script>

    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">

    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

    <style>

        body {

            font-family: 'Inter', sans-serif;

            background-color: #f8fafc; /* slate-50 */

        }

        .table-container {

            height: 55vh;

            overflow-y: scroll;

        }

        .tab-content, .research-tab-content {

            display: none;

        }

        .tab-content.active, .research-tab-content.active {

            display: block;

        }

        .tab-button, .research-tab-button {

            padding: 0.5rem 1rem;

            cursor: pointer;

            border-bottom: 3px solid transparent;

            transition: all 0.3s ease;

            color: #475569; /* slate-600 */

        }

        .tab-button:hover, .research-tab-button:hover {

            color: #0d9488; /* teal-600 */

        }

        .tab-button.active, .research-tab-button.active {

            border-color: #0d9488; /* teal-600 */

            color: #0f172a; /* slate-900 */

            font-weight: 600;

        }

        .modal {

            display: none;

            position: fixed;

            z-index: 1000;

            left: 0;

            top: 0;

            width: 100%;

            height: 100%;

            overflow: auto;

            background-color: rgba(0,0,0,0.5);

        }

        .modal-content {

            background-color: #fefefe;

            margin: 10% auto;

            padding: 24px;

            border: 1px solid #e2e8f0;

            width: 90%;

            max-width: 600px;

            border-radius: 8px;

            box-shadow: 0 5px 15px rgba(0,0,0,0.3);

        }

        .close-button {

            color: #aaa;

            float: right;

            font-size: 28px;

            font-weight: bold;

        }

        .close-button:hover,

        .close-button:focus {

            color: black;

            text-decoration: none;

            cursor: pointer;

        }

        .summary-card {

             border: 1px solid #e2e8f0; /* slate-200 */

        }

        .sortable-header {

            cursor: pointer;

        }

        .sortable-header:hover {

            background-color: #f1f5f9; /* slate-100 */

        }

    </style>

</head>

<body class="bg-slate-50 text-slate-800 flex flex-col min-h-screen">


    <div class="container mx-auto p-4 sm:p-6 lg:p-8 flex-grow">

        <header class="flex items-center justify-between mb-8">

            <div class="flex items-center space-x-4">

                <div class="bg-teal-600 p-3 rounded-lg shadow-md bg-gradient-to-br from-teal-500 to-cyan-600">

                     <svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">

                      <path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />

                    </svg>

                </div>

                <div>

                    <h1 class="text-3xl font-bold text-slate-900">GEDminer</h1>

                    <p class="mt-1 text-slate-700">Your intelligent GEDCOM analysis assistant.</p>

                </div>

            </div>

            <div id="settingsContainer" class="relative hidden">

                <button id="settingsBtn" class="p-2 rounded-full hover:bg-slate-200">

                    <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-slate-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">

                      <path stroke-linecap="round" stroke-linejoin="round" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />

                      <path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />

                    </svg>

                </button>

                <div id="settingsPanel" class="hidden absolute right-0 mt-2 w-72 bg-white rounded-lg shadow-xl p-4 z-10 border">

                    <p class="text-sm font-medium text-slate-800">Home Person</p>

                    <p id="homePersonDisplay" class="text-xs text-slate-600 mb-2">Not set</p>

                    <div id="homePersonSelectWrapper"></div>

                </div>

            </div>

        </header>


        <main class="bg-white rounded-xl shadow-md p-6">

            <!-- Initial Message -->

            <div id="initialMessage" class="text-center text-slate-600 pb-6">

                 <p class="mt-1">Welcome to your browser based genealogy research assistant, here to give you statistics, tips and suggestions to get you further, faster.</p>

                 

                 <div class="grid grid-cols-1 md:grid-cols-3 gap-8 my-8 text-center">

                    <div class="feature-highlight bg-slate-50 p-6 rounded-lg border border-slate-200 shadow-sm">

                        <div class="flex items-center justify-center h-12 w-12 rounded-full bg-teal-100 text-teal-600 mx-auto">

                            <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" /></svg>

                        </div>

                        <h3 class="mt-4 text-lg font-semibold text-slate-800">Analyse</h3>

                        <p class="mt-1 text-sm text-slate-600">Instantly visualize your family data.</p>

                    </div>

                    <div class="feature-highlight bg-slate-50 p-6 rounded-lg border border-slate-200 shadow-sm">

                        <div class="flex items-center justify-center h-12 w-12 rounded-full bg-teal-100 text-teal-600 mx-auto">

                            <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>

                        </div>

                        <h3 class="mt-4 text-lg font-semibold text-slate-800">Validate</h3>

                        <p class="mt-1 text-sm text-slate-600">Find potential errors and inconsistencies.</p>

                    </div>

                    <div class="feature-highlight bg-slate-50 p-6 rounded-lg border border-slate-200 shadow-sm">

                        <div class="flex items-center justify-center h-12 w-12 rounded-full bg-teal-100 text-teal-600 mx-auto">

                            <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" /></svg>

                        </div>

                        <h3 class="mt-4 text-lg font-semibold text-slate-800">Discover</h3>

                        <p class="mt-1 text-sm text-slate-600">Get research suggestions and new insights.</p>

                    </div>

                </div>

                 

                 <p class="mt-4">Ready to analyse your family tree? Upload your GEDCOM file here to begin, or <a href="#" id="sampleLink" class="text-teal-600 hover:underline">try a sample file</a>.</p>

            </div>


            <!-- File Upload Section -->

            <div id="uploadSection" class="mb-4">

                <!-- This container will hold either the large dropzone or the compact bar -->

            </div>


            <div id="privacyNotice" class="hidden text-center text-xs text-slate-500 mb-6 items-center justify-center">

                <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">

                  <path stroke-linecap="round" stroke-linejoin="round" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />

                </svg>

                Your GEDCOM file is processed entirely in your browser. No data is uploaded or stored.

            </div>


            <!-- Analysis Output Section -->

            <div id="analysisOutput" class="hidden">

                 <!-- Tabs -->

                <div class="border-b border-slate-200 mb-6">

                    <nav class="-mb-px flex space-x-6" aria-label="Tabs">

                        <button class="tab-button active" onclick="openTab(event, 'summary')">Summary</button>

                        <button class="tab-button" onclick="openTab(event, 'analysis')">Analysis</button>

                        <button class="tab-button" onclick="openTab(event, 'validation')">Validation</button>

                        <button class="tab-button" onclick="openTab(event, 'research')">Research</button>

                        <button class="tab-button" onclick="openTab(event, 'tools')">Tools</button>

                    </nav>

                </div>


                <!-- Summary Tab -->

                <div id="summary" class="tab-content active">

                    <div class="grid grid-cols-2 md:grid-cols-5 gap-4 mb-6 text-center">

                        <div class="bg-slate-100 p-4 rounded-lg summary-card"><h3 class="text-sm font-medium text-slate-600">Individuals</h3><p class="text-3xl font-bold text-teal-600" id="totalIndividuals">0</p></div>

                        <div class="bg-slate-100 p-4 rounded-lg summary-card"><h3 class="text-sm font-medium text-slate-600">Families</h3><p class="text-3xl font-bold text-teal-600" id="totalFamilies">0</p></div>

                        <div class="bg-slate-100 p-4 rounded-lg summary-card"><h3 class="text-sm font-medium text-slate-600">Surnames</h3><p class="text-3xl font-bold text-teal-600" id="totalSurnames">0</p></div>

                        <div class="bg-slate-100 p-4 rounded-lg summary-card"><h3 class="text-sm font-medium text-slate-600">Avg. Lifespan</h3><p class="text-3xl font-bold text-teal-600" id="avgLifespan">0</p></div>

                        <div class="bg-slate-100 p-4 rounded-lg summary-card"><h3 class="text-sm font-medium text-slate-600">Migrants</h3><p class="text-3xl font-bold text-teal-600" id="totalMigrants">0</p></div>

                    </div>

                    <div>

                        <div class="flex justify-between items-center mb-2">

                            <h2 class="text-xl font-semibold text-slate-800">Individuals <span id="individualsCount" class="text-base font-normal text-slate-700"></span></h2>

                            <input type="text" id="searchFilter" placeholder="Filter by name..." class="w-1/3 p-2 border border-slate-300 rounded-md focus:ring-teal-500 focus:border-teal-500">

                        </div>

                        <div class="table-container rounded-lg border border-slate-200">

                            <table class="min-w-full divide-y divide-slate-200">

                                <thead class="bg-slate-50 sticky top-0">

                                    <tr>

                                        <th class="px-6 py-3 text-left text-xs font-medium text-slate-700 uppercase tracking-wider sortable-header" onclick="sortTable(0)">Name</th>

                                        <th class="px-6 py-3 text-left text-xs font-medium text-slate-700 uppercase tracking-wider sortable-header" onclick="sortTable(1)">Sex</th>

                                        <th class="px-6 py-3 text-left text-xs font-medium text-slate-700 uppercase tracking-wider sortable-header" onclick="sortTable(2)">Birth Date</th>

                                        <th class="px-6 py-3 text-left text-xs font-medium text-slate-700 uppercase tracking-wider sortable-header" onclick="sortTable(3)">Death Date</th>

                                        <th class="px-6 py-3 text-left text-xs font-medium text-slate-700 uppercase tracking-wider">Actions</th>

                                    </tr>

                                </thead>

                                <tbody id="individualsTableBody" class="bg-white divide-y divide-slate-200"></tbody>

                            </table>

                        </div>

                    </div>

                </div>


                <!-- Analysis Tab -->

                <div id="analysis" class="tab-content">

                    <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">

                        <div>

                            <h2 class="text-xl font-semibold mb-4 text-slate-800">Gender Distribution</h2>

                            <div class="p-4 border rounded-lg h-96 border-slate-200"><canvas id="genderDistributionChart"></canvas></div>

                        </div>

                        <div>

                            <h2 class="text-xl font-semibold mb-4 text-slate-800">Age at Death</h2>

                            <div class="p-4 border rounded-lg h-96 border-slate-200"><canvas id="ageAtDeathChart"></canvas></div>

                        </div>

                        <div>

                            <h2 class="text-xl font-semibold mb-4 text-slate-800">Top 10 Surnames</h2>

                            <div class="p-4 border rounded-lg h-96 border-slate-200"><canvas id="surnameChart"></canvas></div>

                        </div>

                        <div>

                            <h2 class="text-xl font-semibold mb-4 text-slate-800">Surnames (Ranks 11-25)</h2>

                            <div id="surnameTableContainer" class="p-4 border rounded-lg h-96 overflow-y-auto border-slate-200"></div>

                        </div>

                         <div>

                            <h2 class="text-xl font-semibold mb-4 text-slate-800">Top 10 Locations</h2>

                            <div class="p-4 border rounded-lg h-96 border-slate-200"><canvas id="locationsChart"></canvas></div>

                        </div>

                        <div>

                            <h2 class="text-xl font-semibold mb-4 text-slate-800">Locations (Ranks 11-25)</h2>

                            <div id="locationsTableContainer" class="p-4 border rounded-lg h-96 overflow-y-auto border-slate-200"></div>

                        </div>

                    </div>

                     <div class="mt-6">

                        <h2 class="text-xl font-semibold mb-4 text-slate-800">Births by Decade</h2>

                        <div class="p-4 border rounded-lg h-80 border-slate-200"><canvas id="birthsByDecadeChart"></canvas></div>

                    </div>

                </div>


                <!-- Data Tab -->

                <div id="validation" class="tab-content">

                     <h2 class="text-xl font-semibold mb-4 text-slate-800">Data Validation</h2>

                     <div id="validationResults" class="space-y-4"></div>

                </div>

                

                <!-- Research Tab -->

                <div id="research" class="tab-content">

                     <h2 class="text-xl font-semibold mb-4 text-slate-800">Research Suggestions</h2>

                     <div id="researchSuggestions">

                        <!-- This will be populated by JS -->

                     </div>

                </div>


                <!-- Tools Tab -->

                <div id="tools" class="tab-content">

                    <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">

                        <div>

                            <h2 class="text-xl font-semibold mb-4 text-slate-800">Relationship Finder</h2>

                            <div class="p-4 border rounded-lg bg-slate-50">

                                <div class="grid grid-cols-2 gap-4 items-end">

                                    <div id="person1Wrapper"></div>

                                    <div id="person2Wrapper"></div>

                                </div>

                                <button id="findRelationshipBtn" class="mt-4 bg-teal-600 text-white px-4 py-2 rounded-md hover:bg-teal-700 text-sm font-medium">Find Relationship</button>

                                <div id="relationshipResult" class="mt-4 text-lg font-semibold text-slate-800"></div>

                            </div>

                        </div>

                        <div>

                            <h2 id="onThisDayTitle" class="text-xl font-semibold mb-4 text-slate-800">On This Day</h2>

                            <div id="onThisDayResult" class="p-4 border rounded-lg bg-slate-50 h-full"></div>

                        </div>

                    </div>

                </div>

            </div>

        </main>

    </div>

    

    <footer class="text-center p-4 text-sm text-slate-600">

        <p>&copy; <span id="copyright-year"></span> <a href="mailto:mykoclelland@gmail.com" class="text-teal-600 hover:underline">Myko Clelland</a></p>

    </footer>


    <!-- Modal for Individual Facts -->

    <div id="factsModal" class="modal">

        <div class="modal-content">

            <span class="close-button" onclick="closeModal()">&times;</span>

            <h2 id="modalName" class="text-2xl font-bold mb-4 text-slate-900"></h2>

            <div id="modalFacts" class="max-h-96 overflow-y-auto"></div>

        </div>

    </div>


    <script>

        document.getElementById('copyright-year').textContent = '2025';

        let allIndividuals = [];

        let allFamilies = [];

        let homePersonId = null;

        let sortState = { table: 'individuals', column: 0, direction: 'asc' };

        

        const TAG_MAP = {

            'BIRT': 'Birth', 'CHR': 'Christening', 'DEAT': 'Death', 'BURI': 'Burial', 'CREM': 'Cremation',

            'ADOP': 'Adoption', 'BAPM': 'Baptism', 'BARM': 'Bar Mitzvah', 'BASM': 'Bas Mitzvah', 'BLES': 'Blessing',

            'CHRA': 'Adult Christening', 'CONF': 'Confirmation', 'FCOM': 'First Communion', 'ORDN': 'Ordination',

            'NATU': 'Naturalization', 'EMIG': 'Emigration', 'IMMI': 'Immigration', 'CENS': 'Census',

            'PROB': 'Probate', 'WILL': 'Will', 'GRAD': 'Graduation', 'RETI': 'Retirement', 'CAST': 'Caste',

            'DSCR': 'Physical Description', 'EDUC': 'Education', 'IDNO': 'Identity Number', 'NATI': 'Nationality',

            'NCHI': 'Children Count', 'NMR': 'Marriage Count', 'OCCU': 'Occupation', 'PROP': 'Property',

            'RELI': 'Religion', 'RESI': 'Residence', 'SSN': 'Social Security Number', 'TITL': 'Title',

            'ANUL': 'Annulment', 'DIV': 'Divorce', 'DIVF': 'Divorce Filed', 'ENGA': 'Engagement',

            'MARB': 'Marriage Banns', 'MARC': 'Marriage Contract', 'MARR': 'Marriage', 'MARL': 'Marriage License',

            'MARS': 'Marriage Settlement'

        };

        const MONTH_MAP = {JAN:0,FEB:1,MAR:2,APR:3,MAY:4,JUN:5,JUL:6,AUG:7,SEP:8,OCT:9,NOV:10,DEC:11};


        // --- TEMPLATES for dynamic UI parts ---

        const uploadContainerTemplate = `

            <div class="mt-1 flex justify-center px-6 pt-5 pb-6 border-2 border-slate-300 border-dashed rounded-md bg-slate-50">

                <div class="space-y-1 text-center">

                    <svg class="mx-auto h-12 w-12 text-slate-400" stroke="currentColor" fill="none" viewBox="0 0 48 48" aria-hidden="true"><path d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4V12a4 4 0 014-4h12l4 4h12a4 4 0 014 4z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /></svg>

                    <div class="flex text-sm text-slate-600">

                        <label for="gedcomFile" class="relative cursor-pointer bg-white rounded-md font-medium text-teal-600 hover:text-teal-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-teal-500">

                            <span>Upload a file</span>

                            <input id="gedcomFile" name="gedcomFile" type="file" class="sr-only" accept=".ged">

                        </label>

                        <p class="pl-1">or drag and drop</p>

                    </div>

                </div>

            </div>`;


        const replaceContainerTemplate = `

             <div class="flex items-center justify-between p-3 border rounded-md bg-slate-50">

                <p class="text-sm font-medium text-slate-700" id="fileNameDisplay"></p>

                <button id="replaceGedcomBtn" class="bg-teal-600 text-white px-4 py-2 rounded-md hover:bg-teal-700 text-sm font-medium">Replace File</button>

             </div>`;


        // --- INITIAL UI SETUP ---

        const uploadSection = document.getElementById('uploadSection');

        uploadSection.innerHTML = uploadContainerTemplate;

        uploadSection.querySelector('#gedcomFile').addEventListener('change', handleFileSelect);


        document.getElementById('settingsBtn').addEventListener('click', () => {

            document.getElementById('settingsPanel').classList.toggle('hidden');

        });

        document.getElementById('findRelationshipBtn').addEventListener('click', findAndDisplayRelationship);

        document.getElementById('searchFilter').addEventListener('keyup', filterIndividuals);

        document.getElementById('sampleLink').addEventListener('click', loadSampleData);



        function handleFileSelect(event) {

            const file = event.target.files[0];

            if (!file) return;

            

            const reader = new FileReader();

            reader.onload = (e) => {

                try {

                    const { individuals, families } = parseGedcom(e.target.result);

                    initializeAppData(individuals, families, file.name);

                } catch (error) {

                    console.error("Error parsing GEDCOM file:", error);

                    alert("There was an error parsing your GEDCOM file.");

                }

            };

            reader.readAsText(file);

        }


        function loadSampleData(event) {

            event.preventDefault();

            const sampleGedcom = `0 HEAD

1 SOUR GEDminer Sample

${Array.from({ length: 260 }, (_, i) => {

    const firstNamesM = ["James", "John", "Robert", "Michael", "William", "David", "Richard", "Joseph", "Thomas", "Charles", "Daniel", "Matthew", "Anthony", "Mark", "Donald", "George"];

    const firstNamesF = ["Mary", "Patricia", "Jennifer", "Linda", "Elizabeth", "Barbara", "Susan", "Jessica", "Sarah", "Karen", "Nancy", "Margaret", "Lisa", "Betty", "Dorothy", "Sandra"];

    const lastNames = ["Campbell", "Smith", "Stewart", "Wilson", "Jones", "Robertson", "Thompson", "Anderson", "MacDonald", "Scott", "Reid", "Murray", "Taylor", "Clark", "Wright", "Walker", "Martin", "Hall", "White", "King", "Green", "Baker", "Adams", "Nelson", "Carter", "Mitchell", "Perez", "Roberts", "Turner"];

    const places = ["Glasgow, Scotland", "Edinburgh, Scotland", "London, England", "Dublin, Ireland", "New York, USA", "Boston, USA", "Cardiff, Wales", "Belfast, Northern Ireland", "Manchester, England", "Liverpool, England", "Toronto, Canada", "Sydney, Australia"];

    

    const sex = Math.random() > 0.48 ? 'F' : 'M'; // Skewed towards female

    const firstName = sex === 'M' ? firstNamesM[i % firstNamesM.length] : firstNamesF[i % firstNamesF.length];

    

    let lastName;

    if (i < 50) lastName = "Campbell";

    else if (i < 90) lastName = "Smith";

    else if (i < 120) lastName = "Stewart";

    else if (i < 140) lastName = "Wilson";

    else if (i < 155) lastName = "Jones";

    else if (i < 170) lastName = "Robertson";

    else if (i < 180) lastName = "Thompson";

    else if (i < 190) lastName = "Anderson";

    else if (i < 200) lastName = "MacDonald";

    else if (i < 210) lastName = "Scott";

    else if (i < 218) lastName = "Reid";

    else if (i < 226) lastName = "Murray";

    else if (i < 233) lastName = "Taylor";

    else if (i < 240) lastName = "Clark";

    else lastName = lastNames[i % lastNames.length];


    const birthYear = 1750 + Math.floor(Math.random() * 150);

    const deathYear = birthYear + 20 + Math.floor(Math.random() * 70);


    let famc = '';

    if (i > 1) {

        famc = `1 FAMC @F${Math.floor((i-1) / 2) + 1}@`;

    }

    

    let fams = '';

    if (i < 130 && i % 5 !== 0) { // Some people don't marry

        fams = `1 FAMS @F${i + 1}@`;

    }


    return `0 @I${i + 1}@ INDI

1 NAME ${firstName} /${lastName}/

1 SEX ${sex}

1 BIRT

2 DATE ${Math.floor(Math.random() * 28) + 1} JAN ${birthYear}

2 PLAC ${places[i % places.length]}

${i < 200 ? `1 DEAT

2 DATE ${Math.floor(Math.random() * 28) + 1} MAR ${deathYear}

2 PLAC ${places[(i+3) % places.length]}` : ''}

${famc}

${fams}

1 SOUR

2 TITL Sample Data`;

}).join('\n')}

${Array.from({ length: 130 }, (_, i) => {

    const marrYear = 1770 + Math.floor(Math.random() * 150);

    const husbandIndex = i * 2 + 1;

    const wifeIndex = i * 2 + 2;

    

    if (husbandIndex > 260 || wifeIndex > 260) return '';

    

    const child1Index = husbandIndex + 2;

    const child2Index = husbandIndex + 3;


    let marrTag = '';

    if (i % 4 !== 0) { // Some families are unmarried

        marrTag = `1 MARR

2 DATE ${marrYear}`;

    }


    return `0 @F${i + 1}@ FAM

1 HUSB @I${husbandIndex}@

1 WIFE @I${wifeIndex}@

${child1Index <= 260 ? `1 CHIL @I${child1Index}@` : ''}

${child2Index <= 260 ? `1 CHIL @I${child2Index}@` : ''}

${marrTag}`;

}).join('\n')}`;

            const { individuals, families } = parseGedcom(sampleGedcom);

            initializeAppData(individuals, families, "sample.ged");

        }


        function initializeAppData(individuals, families, fileName) {

            uploadSection.innerHTML = replaceContainerTemplate;

            document.getElementById('fileNameDisplay').textContent = fileName;

            document.getElementById('replaceGedcomBtn').addEventListener('click', () => {

                uploadSection.innerHTML = uploadContainerTemplate;

                uploadSection.querySelector('#gedcomFile').addEventListener('change', handleFileSelect);

            });


            document.getElementById('initialMessage').classList.add('hidden');

            document.getElementById('privacyNotice').style.display = 'flex';

            document.getElementById('analysisOutput').classList.remove('hidden');

            document.getElementById('settingsContainer').classList.remove('hidden');

            

            allIndividuals = individuals;

            allFamilies = families;

            if (allIndividuals.length > 0) {

                homePersonId = allIndividuals[0].id;

            }

            displayAnalysis(individuals, families);

            populateSelectDropdowns();

            updateHomePersonDisplay();

            findEventsOnThisDay();

        }


        function parseGedcom(content) {

            const lines = content.split(/\r\n|\n/).map(line => {

                const parts = line.trim().split(' ');

                return { level: parseInt(parts[0], 10), tag: parts[1] || '', value: parts.slice(2).join(' ') };

            }).filter(l => !isNaN(l.level));


            const individuals = [], families = [];

            for (let i = 0; i < lines.length; ) {

                const block = getRecordBlock(lines, i);

                if (lines[i].value.includes('INDI')) individuals.push(processIndividual(block));

                else if (lines[i].value.includes('FAM')) families.push(processFamily(block));

                i += block.length || 1;

            }

            return { individuals, families };

        }


        function getRecordBlock(lines, startIndex) {

            const block = [lines[startIndex]];

            let i = startIndex + 1;

            while (i < lines.length && lines[i].level !== 0) {

                block.push(lines[i]);

                i++;

            }

            return block;

        }

        

        function processRecord(block) {

            const record = { id: block[0].tag, events: [], famc: null, fams: [], husband: null, wife: null, children: [] };

            for (let i = 1; i < block.length; i++) {

                const line = block[i];

                if (line.level !== 1) continue;

                switch (line.tag) {

                    case 'NAME': 

                        const nameMatch = line.value.match(/(.*?)\s\/(.*?)\//);

                        if (nameMatch && nameMatch.length === 3) {

                            record.name = `${nameMatch[2].trim()}, ${nameMatch[1].trim()}`;

                        } else {

                            record.name = line.value.replace(/\//g, '').trim();

                        }

                        break;

                    case 'SEX': record.sex = line.value; break;

                    case 'FAMC': record.famc = line.value; break;

                    case 'FAMS': record.fams.push(line.value); break;

                    case 'HUSB': record.husband = line.value; break;

                    case 'WIFE': record.wife = line.value; break;

                    case 'CHIL': record.children.push(line.value); break;

                }

                if (TAG_MAP[line.tag]) {

                    const event = { type: line.tag, value: line.value, date: null, place: null, sources: [] };

                    for (let j = i + 1; j < block.length && block[j].level > line.level; j++) {

                        if (block[j].level === line.level + 1) {

                            if (block[j].tag === 'DATE') event.date = block[j].value;

                            if (block[j].tag === 'PLAC') event.place = block[j].value;

                            if (block[j].tag === 'SOUR') event.sources.push(block[j].value);

                        }

                    }

                    record.events.push(event);

                }

            }

            return record;

        }


        function processIndividual(block) {

            const record = processRecord(block);

            return {

                id: record.id, name: record.name || 'Unknown', sex: record.sex || 'U',

                birth: record.events.find(e => e.type === 'BIRT')?.date || null,

                death: record.events.find(e => e.type === 'DEAT')?.date || null,

                events: record.events, famc: record.famc, fams: record.fams

            };

        }


        function processFamily(block) { return processRecord(block); }


        function displayAnalysis(individuals, families) {

            document.getElementById('totalIndividuals').textContent = individuals.length;

            document.getElementById('totalFamilies').textContent = families.length;

            const surnames = individuals.map(ind => (ind.name.split(',')[0]).trim()).filter(Boolean);

            document.getElementById('totalSurnames').textContent = new Set(surnames).size;

            document.getElementById('individualsCount').textContent = `(${individuals.length})`;

            

            renderIndividualsTable(individuals);


            calculateAndDisplayMetrics(individuals, families);

            runValidation(individuals, families);

            generateResearchSuggestions(individuals);

        }

        

        function renderIndividualsTable(individuals) {

            const tableBody = document.getElementById('individualsTableBody');

            tableBody.innerHTML = ''; 

            individuals.forEach(ind => {

                const row = document.createElement('tr');

                row.innerHTML = `

                    <td class="px-6 py-4 whitespace-nowrap"><div class="text-sm font-medium text-slate-900">${ind.name}</div></td>

                    <td class="px-6 py-4 whitespace-nowrap"><div class="text-sm text-slate-800">${ind.sex}</div></td>

                    <td class="px-6 py-4 whitespace-nowrap"><div class="text-sm text-slate-800">${formatGedcomDate(ind.birth) || '-'}</div></td>

                    <td class="px-6 py-4 whitespace-nowrap"><div class="text-sm text-slate-800">${formatGedcomDate(ind.death) || '-'}</div></td>

                    <td class="px-6 py-4 whitespace-nowrap text-sm font-medium"><button onclick="showFactsModal('${ind.id}')" class="text-teal-600 hover:text-teal-700">View Facts</button></td>

                `;

                tableBody.appendChild(row);

            });

        }


        function formatGedcomDate(dateStr) {

            if (!dateStr) return null;

            const parts = dateStr.split(' ');

            if (parts.length > 1 && MONTH_MAP[parts[1].toUpperCase()]) {

                parts[1] = parts[1].charAt(0).toUpperCase() + parts[1].slice(1).toLowerCase();

            }

            return parts.join(' ');

        }

        

        function parseGedcomDate(dateStr) {

            if (!dateStr) return null;

            const cleanDate = dateStr.toUpperCase().replace(/ABT|BEF|AFT|EST|CAL/g, '').trim();

            const parts = cleanDate.split(' ');

            if (parts.length === 3) {

                const day = parseInt(parts[0]);

                const month = MONTH_MAP[parts[1]];

                const year = parseInt(parts[2]);

                if (!isNaN(day) && month !== undefined && !isNaN(year)) return new Date(year, month, day);

            } else if (parts.length === 2) {

                const month = MONTH_MAP[parts[0]];

                const year = parseInt(parts[1]);

                if (month !== undefined && !isNaN(year)) return new Date(year, month, 1);

            } else if (parts.length === 1 && !isNaN(parseInt(parts[0]))) {

                return new Date(parseInt(parts[0]), 0, 1);

            }

            return null;

        }


        function getCountry(placeStr) {

            if (!placeStr) return null;

            return placeStr.split(',').pop().trim();

        }


        function calculateAndDisplayMetrics(individuals, families) {

            let totalAge = 0, peopleWithLifespan = 0, migrants = 0;

            let maleCount = 0, femaleCount = 0;

            const agesAtDeath = [];

            const locationCounts = {};


            individuals.forEach(ind => {

                const birthYear = getYear(ind.birth);

                const deathYear = getYear(ind.death);

                if (birthYear && deathYear) {

                    const age = deathYear - birthYear;

                    if (age >= 0) {

                        agesAtDeath.push(age);

                        totalAge += age;

                        peopleWithLifespan++;

                    }

                }

                const birthPlace = ind.events.find(e => e.type === 'BIRT')?.place;

                const deathPlace = ind.events.find(e => e.type === 'DEAT')?.place;

                if (birthPlace) locationCounts[birthPlace] = (locationCounts[birthPlace] || 0) + 1;

                if (deathPlace) locationCounts[deathPlace] = (locationCounts[deathPlace] || 0) + 1;

                

                const birthCountry = getCountry(birthPlace);

                const deathCountry = getCountry(deathPlace);

                if (birthCountry && deathCountry && birthCountry.toLowerCase() !== deathCountry.toLowerCase()) migrants++;

                

                if (ind.sex === 'M') maleCount++;

                if (ind.sex === 'F') femaleCount++;

            });

            document.getElementById('avgLifespan').textContent = peopleWithLifespan > 0 ? (totalAge / peopleWithLifespan).toFixed(1) : 'N/A';

            document.getElementById('totalMigrants').textContent = migrants;


            // Gender Chart

            drawChart('genderDistributionChart', 'bar', ['Male', 'Female'], [maleCount, femaleCount], 'Gender Distribution', ['rgba(13, 148, 136, 0.7)', 'rgba(13, 148, 136, 0.7)']);

            

            // Age at Death Chart

            const ageBins = Array(11).fill(0); // 0-9, 10-19, ..., 100+

            agesAtDeath.forEach(age => {

                const binIndex = Math.min(Math.floor(age / 10), 10);

                ageBins[binIndex]++;

            });

            const ageLabels = Array.from({length: 10}, (_, i) => `${i*10}-${i*10+9}`);

            ageLabels.push('100+');

            drawChart('ageAtDeathChart', 'bar', ageLabels, ageBins, 'Age at Death');



            const surnameCounts = individuals.reduce((acc, ind) => {

                const surname = (ind.name.split(',')[0]).trim();

                if (surname) acc[surname] = (acc[surname] || 0) + 1;

                return acc;

            }, {});

            const sortedSurnames = Object.entries(surnameCounts).sort((a, b) => b[1] - a[1]);

            

            drawChart('surnameChart', 'bar', sortedSurnames.slice(0, 10).map(d => d[0]), sortedSurnames.slice(0, 10).map(d => d[1]), 'Surname Count');

            displayFrequencyTable('surnameTableContainer', sortedSurnames.slice(10, 25));


            const sortedLocations = Object.entries(locationCounts).sort((a, b) => b[1] - a[1]);

            drawChart('locationsChart', 'bar', sortedLocations.slice(0, 10).map(d => d[0]), sortedLocations.slice(0, 10).map(d => d[1]), 'Location Count', null, true);

            displayFrequencyTable('locationsTableContainer', sortedLocations.slice(10, 25), 'Location');


            const birthsByDecade = individuals.reduce((acc, ind) => {

                const birthYear = getYear(ind.birth);

                if (birthYear) acc[Math.floor(birthYear / 10) * 10] = (acc[Math.floor(birthYear / 10) * 10] || 0) + 1;

                return acc;

            }, {});

            const sortedDecades = Object.entries(birthsByDecade).sort((a,b) => parseInt(a[0]) - parseInt(b[0]));

            drawChart('birthsByDecadeChart', 'line', sortedDecades.map(d => d[0] + 's'), sortedDecades.map(d => d[1]), 'Number of Births');

        }

        

        function displayFrequencyTable(containerId, data, name = "Surname") {

            const container = document.getElementById(containerId);

            if (data.length === 0) {

                container.innerHTML = `<p class="text-center text-slate-500">No additional ${name.toLowerCase()}s to display.</p>`;

                return;

            }

            let tableHTML = `<table class="min-w-full divide-y divide-slate-200">

                                <thead class="bg-slate-50"><tr>

                                    <th class="px-4 py-2 text-left text-xs font-medium text-slate-700 uppercase">Rank</th>

                                    <th class="px-4 py-2 text-left text-xs font-medium text-slate-700 uppercase">${name}</th>

                                    <th class="px-4 py-2 text-left text-xs font-medium text-slate-700 uppercase">Count</th>

                                </tr></thead><tbody class="bg-white divide-y divide-slate-200">`;

            data.forEach((item, index) => {

                tableHTML += `<tr>

                                <td class="px-4 py-2 text-sm text-slate-700">${index + 11}</td>

                                <td class="px-4 py-2 text-sm font-medium text-slate-800">${item[0]}</td>

                                <td class="px-4 py-2 text-sm text-slate-700">${item[1]}</td>

                              </tr>`;

            });

            tableHTML += '</tbody></table>';

            container.innerHTML = tableHTML;

        }


        function getYear(d) { return parseGedcomDate(d)?.getFullYear() || null; }


        function runValidation(individuals, families) {

            const errors = [];

            const individualsMap = new Map(individuals.map(i => [i.id, i]));


            individuals.forEach(ind => {

                const birthDate = parseGedcomDate(ind.birth);

                const deathDate = parseGedcomDate(ind.death);

                if (birthDate && deathDate && deathDate < birthDate) {

                    errors.push({ type: 'Died Before Born', name: ind.name, details: `Born: ${formatGedcomDate(ind.birth)}, Died: ${formatGedcomDate(ind.death)}` });

                }

            });


            families.forEach(fam => {

                const husband = individualsMap.get(fam.husband);

                const wife = individualsMap.get(fam.wife);

                const marriageEvent = fam.events.find(e => e.type === 'MARR');

                const marriageDateStr = marriageEvent?.date;

                const marriageDate = parseGedcomDate(marriageDateStr);


                if (husband?.death && marriageDate) {

                    const deathDate = parseGedcomDate(husband.death);

                    if (deathDate && marriageDate > deathDate) {

                        const isAmbiguous = marriageDateStr.trim().split(' ').length < 3 && deathDate.getFullYear() === marriageDate.getFullYear() && deathDate.getMonth() === marriageDate.getMonth();

                        if (!isAmbiguous) errors.push({ type: 'Marriage After Death', name: husband.name, details: `Died: ${formatGedcomDate(husband.death)}, Married: ${formatGedcomDate(marriageDateStr)}` });

                    }

                }

                if (wife?.death && marriageDate) {

                     const deathDate = parseGedcomDate(wife.death);

                    if (deathDate && marriageDate > deathDate) {

                        const isAmbiguous = marriageDateStr.trim().split(' ').length < 3 && deathDate.getFullYear() === marriageDate.getFullYear() && deathDate.getMonth() === marriageDate.getMonth();

                        if (!isAmbiguous) errors.push({ type: 'Marriage After Death', name: wife.name, details: `Died: ${formatGedcomDate(wife.death)}, Married: ${formatGedcomDate(marriageDateStr)}` });

                    }

                }


                fam.children.forEach(childId => {

                    const child = individualsMap.get(childId);

                    if (!child?.birth) return;

                    const childBirthDate = parseGedcomDate(child.birth);

                    if (!childBirthDate) return;


                    if (husband?.death) {

                        const fatherDeathDate = parseGedcomDate(husband.death);

                        if (fatherDeathDate) {

                            const conceptionCutoff = new Date(fatherDeathDate);

                            conceptionCutoff.setMonth(conceptionCutoff.getMonth() + 9);

                            if (childBirthDate > conceptionCutoff) {

                                errors.push({ type: 'Born >9 Months After Father\'s Death', name: child.name, details: `Born: ${formatGedcomDate(child.birth)}, Father Died: ${formatGedcomDate(husband.death)}` });

                            }

                        }

                    }

                    if (wife?.death) {

                        const motherDeathDate = parseGedcomDate(wife.death);

                        if (motherDeathDate && childBirthDate > motherDeathDate) {

                            errors.push({ type: 'Born After Mother\'s Death', name: child.name, details: `Born: ${formatGedcomDate(child.birth)}, Mother Died: ${formatGedcomDate(wife.death)}` });

                        }

                    }

                });

            });

            displayValidationResults(errors);

        }


        function generateResearchSuggestions(individuals) {

            const container = document.getElementById('researchSuggestions');

            const missingBirths = individuals.filter(ind => !ind.birth);

            const missingDeaths = individuals.filter(ind => !ind.death && getYear(ind.birth) < (new Date().getFullYear() - 110));

            const missingSources = individuals.filter(ind => ind.events.length > 0 && ind.events.every(e => e.sources.length === 0));



            if (missingBirths.length === 0 && missingDeaths.length === 0 && missingSources.length === 0) {

                container.innerHTML = '<div class="text-center text-green-600 bg-green-50 p-4 rounded-lg">No obvious research suggestions found. Great work!</div>';

                return;

            }

            

            container.innerHTML = `

                <div class="border-b border-slate-200 mb-4">

                    <nav class="-mb-px flex space-x-6" aria-label="Research Tabs">

                        <button class="research-tab-button active" onclick="openResearchTab(event, 'missingBirths')">Missing Births (${missingBirths.length})</button>

                        <button class="research-tab-button" onclick="openResearchTab(event, 'missingDeaths')">Missing Deaths (${missingDeaths.length})</button>

                        <button class="research-tab-button" onclick="openResearchTab(event, 'missingSources')">Unsourced People (${missingSources.length})</button>

                    </nav>

                </div>

                <p id="researchDescription" class="text-sm text-slate-600 mb-4"></p>

                <div id="missingBirths" class="research-tab-content active"></div>

                <div id="missingDeaths" class="research-tab-content"></div>

                <div id="missingSources" class="research-tab-content"></div>

            `;


            populateSuggestionTable('missingBirths', missingBirths, { col1: 'Name', col2: 'Death Date' }, (p) => formatGedcomDate(p.death));

            populateSuggestionTable('missingDeaths', missingDeaths, { col1: 'Name', col2: 'Birth Date' }, (p) => formatGedcomDate(p.birth));

            populateSuggestionTable('missingSources', missingSources, { col1: 'Name', col2: 'Known Dates' }, (p) => `${formatGedcomDate(p.birth) || '????'} - ${formatGedcomDate(p.death) || '????'}`);

            

            document.getElementById('researchDescription').textContent = "This list shows every person in your file who does not have a birth date recorded. Adding a birth date is a key step in building a complete profile.";


        }


        function populateSuggestionTable(elementId, data, headers, detailFn) {

            const container = document.getElementById(elementId);

            if(data.length === 0) {

                container.innerHTML = '<p class="text-center text-slate-500 py-4">None found.</p>';

                return;

            }

            let tableHTML = `<div class="table-container border rounded-lg"><table class="min-w-full divide-y divide-slate-200">

                                <thead class="bg-slate-50 sticky top-0"><tr>

                                    <th class="px-6 py-3 text-left text-xs font-medium text-slate-700 uppercase">${headers.col1}</th>

                                    <th class="px-6 py-3 text-left text-xs font-medium text-slate-700 uppercase">${headers.col2}</th>

                                </tr></thead><tbody class="bg-white divide-y divide-slate-200">`;

            

            data.forEach(person => {

                tableHTML += `<tr>

                                <td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-slate-900">${person.name}</td>

                                <td class="px-6 py-4 whitespace-nowrap text-sm text-slate-800">${detailFn(person) || '-'}</td>

                             </tr>`;

            });

            tableHTML += '</tbody></table></div>';

            container.innerHTML = tableHTML;

        }


        function displayValidationResults(errors) {

            const container = document.getElementById('validationResults');

            container.innerHTML = '';

            if (errors.length === 0) {

                container.innerHTML = '<div class="text-center text-green-600 bg-green-50 p-4 rounded-lg">No validation issues found.</div>';

                return;

            }

            const errorsByType = errors.reduce((acc, err) => {

                if (!acc[err.type]) acc[err.type] = [];

                acc[err.type].push(err);

                return acc;

            }, {});

            for (const type in errorsByType) {

                const section = document.createElement('div');

                section.className = 'p-4 border rounded-lg bg-red-50';

                let tableHTML = `<h3 class="font-semibold text-lg mb-2 text-red-800">${type} (${errorsByType[type].length})</h3><table class="min-w-full divide-y divide-slate-200"><thead class="bg-slate-50"><tr><th class="px-6 py-3 text-left text-xs font-medium text-slate-700 uppercase tracking-wider">Name</th><th class="px-6 py-3 text-left text-xs font-medium text-slate-700 uppercase tracking-wider">Details</th></tr></thead><tbody class="bg-white divide-y divide-slate-200"></tbody>`;

                errorsByType[type].forEach(err => { tableHTML += `<tr><td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-slate-900">${err.name.replace(/\//g, '')}</td><td class="px-6 py-4 whitespace-nowrap text-sm text-slate-800">${err.details}</td></tr>`; });

                tableHTML += '</tbody></table>';

                section.innerHTML = tableHTML;

            }

        }

        

        function filterIndividuals() {

            const filter = document.getElementById('searchFilter').value.toLowerCase();

            const rows = document.getElementById('individualsTableBody').getElementsByTagName('tr');

            let visibleCount = 0;

            for (let i = 0; i < rows.length; i++) {

                const name = rows[i].getElementsByTagName('td')[0].textContent.toLowerCase();

                if (name.includes(filter)) {

                    rows[i].style.display = '';

                    visibleCount++;

                } else {

                    rows[i].style.display = 'none';

                }

            }

            const countSpan = document.getElementById('individualsCount');

            if(filter) {

                countSpan.textContent = `(${visibleCount}/${allIndividuals.length})`;

            } else {

                countSpan.textContent = `(${allIndividuals.length})`;

            }

        }


        function drawChart(canvasId, type, labels, data, label, colors, isHorizontal = false) {

            const ctx = document.getElementById(canvasId).getContext('2d');

            if (window[canvasId + 'Instance']) window[canvasId + 'Instance'].destroy();

            

            let chartConfig = {

                type: type,

                data: {

                    labels: labels,

                    datasets: [{

                        label: label,

                        data: data,

                        backgroundColor: colors || 'rgba(13, 148, 136, 0.7)', // teal-600 with opacity

                        borderColor: colors || '#0f766e', // teal-700

                        borderWidth: 2

                    }]

                },

                options: {

                    responsive: true,

                    maintainAspectRatio: false,

                    scales: { y: { beginAtZero: true } }

                }

            };


            if (isHorizontal) {

                chartConfig.options.indexAxis = 'y';

            }


            if (type === 'line') {

                const gradient = ctx.createLinearGradient(0, 0, 0, 400);

                gradient.addColorStop(0, 'rgba(13, 148, 136, 0.4)');

                gradient.addColorStop(1, 'rgba(13, 148, 136, 0)');

                chartConfig.data.datasets[0].backgroundColor = gradient;

                chartConfig.data.datasets[0].borderColor = '#0d9488'; // teal-600

                chartConfig.data.datasets[0].fill = true;

                chartConfig.data.datasets[0].tension = 0.3;

            }


            window[canvasId + 'Instance'] = new Chart(ctx, chartConfig);

        }


        function showFactsModal(id) {

            const ind = allIndividuals.find(i => i.id === id);

            document.getElementById('modalName').textContent = ind.name;

            const factsContainer = document.getElementById('modalFacts');

            factsContainer.innerHTML = '';

            const table = document.createElement('table');

            table.className = 'min-w-full divide-y divide-slate-200';

            table.innerHTML = `<thead class="bg-slate-50"><tr><th class="px-4 py-2 text-left text-xs font-medium text-slate-700 uppercase">Fact</th><th class="px-4 py-2 text-left text-xs font-medium text-slate-700 uppercase">Date</th><th class="px-4 py-2 text-left text-xs font-medium text-slate-700 uppercase">Place / Details</th></tr></thead><tbody class="bg-white divide-y divide-slate-200"></tbody>`;

            ind.events.forEach(e => table.querySelector('tbody').insertRow().innerHTML = `<td class="px-4 py-2 text-sm font-medium">${TAG_MAP[e.type] || e.type}</td><td class="px-4 py-2 text-sm">${formatGedcomDate(e.date)||'-'}</td><td class="px-4 py-2 text-sm">${e.place||e.value||'-'}</td>`);

            factsContainer.appendChild(table);

            document.getElementById('factsModal').style.display = 'block';

        }


        function closeModal() { document.getElementById('factsModal').style.display = 'none'; }

        window.onclick = (e) => { if (e.target == document.getElementById('factsModal')) closeModal(); }

        

        function openTab(evt, tabName) {

            document.querySelectorAll('.tab-content').forEach(t => t.style.display = "none");

            document.querySelectorAll('.tab-button').forEach(b => b.classList.remove('active'));

            document.getElementById(tabName).style.display = "block";

            evt.currentTarget.classList.add('active');

        }


        function openResearchTab(evt, tabName) {

            document.querySelectorAll('.research-tab-content').forEach(t => t.style.display = "none");

            document.querySelectorAll('.research-tab-button').forEach(b => b.classList.remove('active'));

            document.getElementById(tabName).style.display = "block";

            evt.currentTarget.classList.add('active');


            const researchDescriptions = {

                missingBirths: "This list shows every person in your file who does not have a birth date recorded. Adding a birth date is a key step in building a complete profile.",

                missingDeaths: "This list shows people born over 110 years ago who do not have a death date. They are likely deceased, and finding their death record could provide new information.",

                missingSources: "This list shows people who have facts (like births, marriages, or deaths) recorded, but none of those facts have a source citation. Adding sources is crucial for good genealogy."

            };

            document.getElementById('researchDescription').textContent = researchDescriptions[tabName];

        }


        function updateHomePersonDisplay() {

            const person = allIndividuals.find(ind => ind.id === homePersonId);

            const display = document.getElementById('homePersonDisplay');

            if (person) {

                display.textContent = `${person.name} (${person.id})`;

            } else {

                display.textContent = `Not set`;

            }

        }


        function setHomePerson(newId) {

            if (allIndividuals.some(ind => ind.id === newId)) {

                homePersonId = newId;

                updateHomePersonDisplay();

            }

        }


        function populateSelectDropdowns() {

            const person1Wrapper = document.getElementById('person1Wrapper');

            const person2Wrapper = document.getElementById('person2Wrapper');

            const homePersonWrapper = document.getElementById('homePersonSelectWrapper');


            createSearchableDropdown(person1Wrapper, 'person1', 'Person 1', allIndividuals);

            createSearchableDropdown(person2Wrapper, 'person2', 'Person 2', allIndividuals);

            createSearchableDropdown(homePersonWrapper, 'homePersonSelect', 'Home Person', allIndividuals, (id) => {

                setHomePerson(id);

                document.getElementById('settingsPanel').classList.add('hidden');

            });

        }


        function findAndDisplayRelationship() {

            const id1 = document.getElementById('person1Wrapper').dataset.value;

            const id2 = document.getElementById('person2Wrapper').dataset.value;

            const resultDiv = document.getElementById('relationshipResult');

            resultDiv.textContent = calculateRelationship(id1, id2);

        }


        function getAncestorPath(startId, individualsMap, familiesMap) {

            const path = new Map();

            const queue = [{ id: startId, level: 0 }];

            path.set(startId, 0);

            

            let head = 0;

            while(head < queue.length) {

                const { id, level } = queue[head++];

                const person = individualsMap.get(id);

                if (!person || !person.famc) continue;

                const family = familiesMap.get(person.famc);

                if (!family) continue;


                const parents = [family.husband, family.wife].filter(Boolean);

                parents.forEach(parentId => {

                    if (!path.has(parentId)) {

                        path.set(parentId, level + 1);

                        queue.push({ id: parentId, level: level + 1 });

                    }

                });

            }

            return path;

        }


        function calculateBloodRelationship(id1, id2, individualsMap, familiesMap) {

            const path1 = getAncestorPath(id1, individualsMap, familiesMap);

            const path2 = getAncestorPath(id2, individualsMap, familiesMap);


            let commonAncestor = null;

            let level1 = -1, level2 = -1;

            let minLevelSum = Infinity;


            for (const [ancestorId, l1] of path1.entries()) {

                if (path2.has(ancestorId)) {

                    const l2 = path2.get(ancestorId);

                    if (l1 + l2 < minLevelSum) {

                        minLevelSum = l1 + l2;

                        commonAncestor = ancestorId;

                        level1 = l1;

                        level2 = l2;

                    }

                }

            }


            if (!commonAncestor) return null;


            if (level1 === 0) return `Descendant (${level2} generations)`;

            if (level2 === 0) return `Ancestor (${level1} generations)`;

            

            const cousinLevel = Math.min(level1, level2) - 1;

            const removalLevel = Math.abs(level1 - level2);


            if (cousinLevel === 0) return "Sibling";


            const cousinOrdinal = cousinLevel === 1 ? '1st' : cousinLevel === 2 ? '2nd' : cousinLevel === 3 ? '3rd' : `${cousinLevel}th`;

            

            if (removalLevel === 0) {

                return `${cousinOrdinal} Cousin`;

            } else {

                return `${cousinOrdinal} Cousin, ${removalLevel}x removed`;

            }

        }


        function calculateRelationship(id1, id2) {

            if (!id1 || !id2) return "Please select two people.";

            if (id1 === id2) return "Self";


            const individualsMap = new Map(allIndividuals.map(i => [i.id, i]));

            const familiesMap = new Map(allFamilies.map(f => [f.id, f]));


            // 1. Check for direct blood relationship

            const bloodRel = calculateBloodRelationship(id1, id2, individualsMap, familiesMap);

            if (bloodRel) return bloodRel;


            // 2. Check for spousal relationship

            const person1 = individualsMap.get(id1);

            for (const famsId of person1.fams) {

                const family = familiesMap.get(famsId);

                if (family && (family.husband === id2 || family.wife === id2)) {

                    return "Spouse";

                }

            }


            // 3. Check for in-law relationships (spouse of a blood relative)

            const person1Spouses = new Set();

            person1.fams.forEach(famsId => {

                const family = familiesMap.get(famsId);

                if(family) {

                    if(family.husband && family.husband !== id1) person1Spouses.add(family.husband);

                    if(family.wife && family.wife !== id1) person1Spouses.add(family.wife);

                }

            });


            for (const spouseId of person1Spouses) {

                const rel = calculateBloodRelationship(spouseId, id2, individualsMap, familiesMap);

                if (rel) {

                    const spouseName = individualsMap.get(spouseId).name;

                    return `Spouse of ${spouseName} (${rel})`;

                }

            }


            const person2Spouses = new Set();

            const person2 = individualsMap.get(id2);

            person2.fams.forEach(famsId => {

                const family = familiesMap.get(famsId);

                if(family) {

                    if(family.husband && family.husband !== id2) person2Spouses.add(family.husband);

                    if(family.wife && family.wife !== id2) person2Spouses.add(family.wife);

                }

            });


            for (const spouseId of person2Spouses) {

                const rel = calculateBloodRelationship(id1, spouseId, individualsMap, familiesMap);

                if (rel) {

                    const spouseName = individualsMap.get(spouseId).name;

                    return `${rel} of ${spouseName} (Spouse)`;

                }

            }


            return "No relationship found.";

        }

        

        function findEventsOnThisDay() {

            const container = document.getElementById('onThisDayResult');

            const today = new Date();

            const month = today.getMonth();

            const day = today.getDate();

            const eventsToday = [];

            

            const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];

            document.getElementById('onThisDayTitle').textContent = `On This Day (${day} ${monthNames[month]})`;



            allIndividuals.forEach(ind => {

                ind.events.forEach(event => {

                    const eventDate = parseGedcomDate(event.date);

                    if (eventDate && eventDate.getMonth() === month && eventDate.getDate() === day) {

                        eventsToday.push({ year: eventDate.getFullYear(), name: ind.name, event: TAG_MAP[event.type] || event.type, personId: ind.id });

                    }

                });

            });


            allFamilies.forEach(fam => {

                const marrEvent = fam.events.find(e => e.type === 'MARR');

                if (marrEvent && marrEvent.date) {

                    const eventDate = parseGedcomDate(marrEvent.date);

                    if (eventDate && eventDate.getMonth() === month && eventDate.getDate() === day) {

                        const husband = allIndividuals.find(i => i.id === fam.husband)?.name || 'Unknown';

                        const wife = allIndividuals.find(i => i.id === fam.wife)?.name || 'Unknown';

                        eventsToday.push({ year: eventDate.getFullYear(), name: `${husband} & ${wife}`, event: 'Marriage', personId: fam.husband || fam.wife });

                    }

                }

            });

            

            eventsToday.sort((a,b) => a.year - b.year);


            if (eventsToday.length === 0) {

                container.innerHTML = `<p class="text-center text-slate-500">No events found for today's date.</p>`;

                return;

            }


            let html = `<div class="table-container border rounded-lg"><table class="min-w-full divide-y divide-slate-200">

                            <thead class="bg-slate-50 sticky top-0"><tr>

                                <th class="px-6 py-3 text-left text-xs font-medium text-slate-700 uppercase">Year</th>

                                <th class="px-6 py-3 text-left text-xs font-medium text-slate-700 uppercase">Event</th>

                                <th class="px-6 py-3 text-left text-xs font-medium text-slate-700 uppercase">Name(s)</th>

                                <th class="px-6 py-3 text-left text-xs font-medium text-slate-700 uppercase">Relationship</th>

                            </tr></thead><tbody class="bg-white divide-y divide-slate-200">`;

            eventsToday.forEach(e => {

                const relationship = e.personId ? calculateRelationship(homePersonId, e.personId) : '';

                html += `<tr>

                            <td class="px-6 py-4 text-sm">${e.year}</td>

                            <td class="px-6 py-4 text-sm font-medium">${e.event}</td>

                            <td class="px-6 py-4 text-sm">${e.name}</td>

                            <td class="px-6 py-4 text-sm text-slate-600">${relationship}</td>

                         </tr>`;

            });

            html += '</tbody></table></div>';

            container.innerHTML = html;

        }


        function createSearchableDropdown(wrapper, id, label, individuals, onChangeCallback) {

            const birthYear = (p) => getYear(p.birth) || '????';

            const deathYear = (p) => getYear(p.death) || '????';

            

            wrapper.innerHTML = `

                <label for="${id}-input" class="block text-sm font-medium text-slate-700">${label}</label>

                <div class="relative mt-1">

                    <input type="text" id="${id}-input" class="w-full p-2 border border-slate-300 rounded-md" autocomplete="off">

                    <div id="${id}-list" class="absolute hidden z-10 w-full bg-white border border-slate-300 rounded-md mt-1 max-h-60 overflow-y-auto"></div>

                </div>

            `;


            const input = wrapper.querySelector(`#${id}-input`);

            const list = wrapper.querySelector(`#${id}-list`);


            const populateList = (filter = '') => {

                list.innerHTML = '';

                individuals

                    .filter(ind => ind.name.toLowerCase().includes(filter.toLowerCase()))

                    .forEach(ind => {

                        const item = document.createElement('div');

                        item.textContent = `${ind.name} (${birthYear(ind)}-${deathYear(ind)})`;

                        item.dataset.value = ind.id;

                        item.className = 'p-2 hover:bg-teal-100 cursor-pointer';

                        item.addEventListener('mousedown', () => {

                            input.value = item.textContent;

                            wrapper.dataset.value = ind.id;

                            list.classList.add('hidden');

                            if (onChangeCallback) onChangeCallback(ind.id);

                        });

                        list.appendChild(item);

                    });

            };


            input.addEventListener('focus', () => {

                populateList(input.value);

                list.classList.remove('hidden');

            });

            input.addEventListener('keyup', () => populateList(input.value));

            input.addEventListener('blur', () => setTimeout(() => list.classList.add('hidden'), 200));

        }


        function sortTable(columnIndex) {

            if (sortState.column === columnIndex) {

                sortState.direction = sortState.direction === 'asc' ? 'desc' : 'asc';

            } else {

                sortState.column = columnIndex;

                sortState.direction = 'asc';

            }


            const sortedIndividuals = [...allIndividuals].sort((a, b) => {

                let valA, valB;

                switch (columnIndex) {

                    case 0: // Name

                        valA = a.name.toLowerCase();

                        valB = b.name.toLowerCase();

                        break;

                    case 1: // Sex

                        valA = a.sex.toLowerCase();

                        valB = b.sex.toLowerCase();

                        break;

                    case 2: // Birth Date

                        valA = getYear(a.birth) || 0;

                        valB = getYear(b.birth) || 0;

                        break;

                    case 3: // Death Date

                        valA = getYear(a.death) || 0;

                        valB = getYear(b.death) || 0;

                        break;

                }


                if (valA < valB) {

                    return sortState.direction === 'asc' ? -1 : 1;

                }

                if (valA > valB) {

                    return sortState.direction === 'asc' ? 1 : -1;

                }

                return 0;

            });


            renderIndividualsTable(sortedIndividuals);

            updateSortIndicators();

        }


        function updateSortIndicators() {

            const headers = document.querySelectorAll('.sortable-header');

            headers.forEach((header, index) => {

                header.innerHTML = header.innerHTML.replace(/ (↑|↓)$/, ''); // Remove old arrow

                if (index === sortState.column) {

                    header.innerHTML += sortState.direction === 'asc' ? ' ↑' : ' ↓';

                }

            });

        }


    </script>

</body>

</html>