#!/usr/bin/env node

/**
 *
 * Copyright 2014-2022 David Herron
 *
 * This file is part of AkashaCMS (http://akashacms.com/).
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

'use strict';

import { program } from 'commander';
import ghpages from 'gh-pages';
import fs from 'node:fs';
import { promises as fsp } from 'node:fs';
import path from 'node:path';
import util from 'node:util';
import * as data from './data.js';
import YAML from 'js-yaml';

const _watchman = import('./cache/watchman.js');

process.title = 'akasharender';
program.version('0.7.5');

program
    .command('copy-assets <configFN>')
    .description('Copy assets into output directory')
    .action(async (configFN) => {
        try {
            const config = (await import(
                path.join(process.cwd(), configFN)
            )).default;
            let akasha = config.akasha;
            await akasha.setup(config);
            await config.copyAssets();
            await akasha.closeCaches();
        } catch (e) {
            console.error(`copy-assets command ERRORED ${e.stack}`);
        }

    });

program
    .command('document <configFN> <documentFN>')
    .description('Show information about a document')
    .action(async (configFN, documentFN) => {

        try {
            const config = (await import(
                path.join(process.cwd(), configFN)
            )).default;
            let akasha = config.akasha;
            await akasha.setup(config);
            const documents = akasha.filecache.documentsCache;
            const doc = await documents.find(documentFN);
            // data: ${doc.data}
            // text: ${doc.text}
            console.log(`
docpath: ${doc.vpath}
fspath: ${doc.fspath}
renderer: ${util.inspect(config.findRendererPath(doc.vpath))}
renderpath: ${doc.renderPath}
mounted: ${doc.mounted}
mountPoint: ${doc.mountPoint}

metadata: ${util.inspect(doc.metadata)}

`);
            await akasha.closeCaches();
        } catch (e) {
            console.error(`document command ERRORED ${e.stack}`);
        }
    });

program
    .command('render-document <configFN> <documentFN>')
    .description('Render a document into output directory')
    .action(async (configFN, documentFN) => {
        try {
            const config = (await import(
                path.join(process.cwd(), configFN)
            )).default;
            let akasha = config.akasha;
            await akasha.setup(config);
            await data.removeAll();
            // console.log(`render-document before renderPath ${documentFN}`);
            let result = await akasha.renderPath(config, documentFN);
            console.log(result);
            await akasha.closeCaches();
        } catch (e) {
            console.error(`render-document command ERRORED ${e.stack}`);
        }
    });

program
    .command('render <configFN>')
    .description('Render a site into output directory')
    .option('--quiet', 'Do not print the rendering report')
    .option('--copy-assets', 'First, copy the assets')
    .option('--results-to <resultFile>', 'Store the results into the named file')
    .option('--perfresults <perfResultsFile>', 'Store the time to render each document')
    .action(async (configFN, cmdObj) => {
        // console.log(`render: akasha: ${util.inspect(akasha)}`);
        try {
            const config = (await import(
                path.join(process.cwd(), configFN)
            )).default;
            let akasha = config.akasha;
            await akasha.setup(config);
            await data.removeAll();
            if (cmdObj.copyAssets) {
                await config.copyAssets();
            }
            let results = await akasha.render(config);
            if (!cmdObj.quiet) {
                for (let result of results) {

                    // TODO --- if AKASHARENDER_TRACE_RENDER then output tracing data
                    // TODO --- also set process.env.GLOBFS_TRACE=1

                    if (result.error) {
                        console.error(result.error);
                    } else {
                        console.log(result.result);
                        // console.log(util.inspect(result.result));
                    }
                }
            }
            if (cmdObj.resultsTo) {
                const output = fs.createWriteStream(cmdObj.resultsTo);
                for (let result of results) {
                    if (result.error) {
                        output.write('****ERROR '+ result.error + '\n');
                    } else {
                        output.write(result.result + '\n');
                        // console.log(util.inspect(result.result));
                    }
                }
                output.close();
            }
            if (cmdObj.perfresults) {
                const output = fs.createWriteStream(cmdObj.perfresults);
                for (let result of results) {
                    if (result.error) {
                        // Ignore
                    } else if (result.result.startsWith('COPY')) {
                        // Ignore
                    } else {
                        let results = result.result.split('\n');
                        let perf = results[0];
                        let matches = perf.match(/.* ==> (.*) \(([0-9\.]+) seconds\)$/);
                        if (!matches) continue;
                        if (matches.length < 3) continue;
                        let fn = matches[1];
                        let time = matches[2];
                        let report = `${time} ${fn}`;
                        for (let i = 1; i < results.length; i++) {
                            let stages = results[i].match(/(FRONTMATTER|FIRST RENDER|SECOND RENDER|MAHABHUTA|RENDERED) ([0-9\.]+) seconds$/);
                            if (!stages || stages.length < 3) continue;
                            report += ` ${stages[1]} ${stages[2]}`;
                        }
                        output.write(`${report}\n`);
                    }
                }
                output.close();
            }
            await akasha.closeCaches();
        } catch (e) {
            console.error(`render command ERRORED ${e.stack}`);
        }
    });

program
    .command('watch <configFN>')
    .description('Track changes to files in a site, and rebuild anything that changes')
    .action(async (configFN, cmdObj) => {
        // console.log(`render: akasha: ${util.inspect(akasha)}`);
        try {
            const config = (await import(
                path.join(process.cwd(), configFN)
            )).default;
            let akasha = config.akasha;
            await akasha.setup(config);
            await data.removeAll();
            // console.log('CALLING config.hookBeforeSiteRendered');
            await config.hookBeforeSiteRendered();
            const watchman = (await _watchman).watchman;
            await watchman(config);
            // await akasha.closeCaches();
        } catch (e) {
            console.error(`watch command ERRORED ${e.stack}`);
        }
    });

program
    .command('gh-pages-publish <configFN>')
    .description('Publish a site using Github Pages.  Takes the rendering destination, adds it into a branch, and pushes that to Github')
    .option('-b, --branch <branchName>', 'The branch to use for publishing to Github')
    .option('-r, --repo <repoURL>', 'The repository URL to use if it must differ from the URL of the local directory')
    .option('--remote <remoteName>', 'The Git remote name to use if it must differ from "origin"')
    .option('--tag <tag>', 'Any tag to add when pushing to Github')
    .option('--message <message>', 'Any Git commit message')
    .option('--username <username>', 'Github user name to use')
    .option('--email <email>', 'Github user email to use')
    .option('--nopush', 'Do not push to Github, only commit')
    .option('--cname <domain>', 'Write out a CNAME file with the domain name')
    .action(async (configFN, cmdObj) => {
        // console.log(`render: akasha: ${util.inspect(akasha)}`);
        try {
            const config = (await import(
                path.join(process.cwd(), configFN)
            )).default;
            let akasha = config.akasha;

            let options: any = {
                dotfiles: true
            };
            if (cmdObj.branch) {
                options.branch = cmdObj.branch;
            }
            if (cmdObj.repoURL) {
                options.repo = cmdObj.repoURL;
            }
            if (cmdObj.remote) {
                options.remote = cmdObj.remote;
            }
            if (cmdObj.cname) {
                options.cname = cmdObj.cname;
            }
            if (cmdObj.tag) {
                options.tag = cmdObj.tag;
            }
            if (cmdObj.message) {
                options.message = cmdObj.message;
            }
            if (cmdObj.username || cmdObj.email) {
                options.user = {};
            }
            if (cmdObj.username) {
                options.user.name = cmdObj.username;
            }
            if (cmdObj.email) {
                options.user.email = cmdObj.email;
            }
            if (cmdObj.nopush) {
                options.push = false;
            }

            options.nojekyll = true;
            options.dotfiles = true;

            // console.log(`gh-pages-publish options ${config.renderDestination} cmdObj ${util.inspect(cmdObj)} options ${util.inspect(options)}`);

            ghpages.publish(config.renderDestination, options, function(err) {

                if (err) console.error(err);
                else console.log('OK');
            });
        } catch (e) {
            console.error(`gh-pages-publish command ERRORED ${e.stack}`);
        }
    });


program
    .command('config <configFN>')
    .description('Print a site configuration')
    .action(async (configFN) => {
        // console.log(`render: akasha: ${util.inspect(akasha)}`);
        try {
            const config = (await import(
                path.join(process.cwd(), configFN)
            )).default;
            let akasha = config.akasha;
            console.log(config);
        } catch (e) {
            console.error(`config command ERRORED ${e.stack}`);
        }
    });

program
    .command('plugins <configFN>')
    .description('List the plugins')
    .action(async (configFN) => {
        // console.log(`render: akasha: ${util.inspect(akasha)}`);
        try {
            const config = (await import(
                path.join(process.cwd(), configFN)
            )).default;
            let akasha = config.akasha;
            console.log(config.plugins);
            
        } catch (e) {
            console.error(`config command ERRORED ${e.stack}`);
        }
    });

program
    .command('docdirs <configFN>')
    .description('List the documents directories in a site configuration')
    .action(async (configFN) => {
        // console.log(`render: akasha: ${util.inspect(akasha)}`);
        try {
            const config = (await import(
                path.join(process.cwd(), configFN)
            )).default;
            let akasha = config.akasha;
            await akasha.setup(config);
            console.log(config.documentDirs);
            await akasha.closeCaches();
        } catch (e) {
            console.error(`docdirs command ERRORED ${e.stack}`);
        }
    });

program
    .command('assetdirs <configFN>')
    .description('List the assets directories in a site configuration')
    .action(async (configFN) => {
        // console.log(`render: akasha: ${util.inspect(akasha)}`);
        try {
            const config = (await import(
                path.join(process.cwd(), configFN)
            )).default;
            let akasha = config.akasha;
            await akasha.setup(config);
            console.log(config.assetDirs);
            await akasha.closeCaches();
        } catch (e) {
            console.error(`assetdirs command ERRORED ${e.stack}`);
        }
    });

program
    .command('partialdirs <configFN>')
    .description('List the partials directories in a site configuration')
    .action(async (configFN) => {
        // console.log(`render: akasha: ${util.inspect(akasha)}`);
        try {
            const config = (await import(
                path.join(process.cwd(), configFN)
            )).default;
            let akasha = config.akasha;
            await akasha.setup(config);
            console.log(config.partialsDirs);
            await akasha.closeCaches();
        } catch (e) {
            console.error(`partialdirs command ERRORED ${e.stack}`);
        }
    });

program
    .command('layoutsdirs <configFN>')
    .description('List the layouts directories in a site configuration')
    .action(async (configFN) => {
        // console.log(`render: akasha: ${util.inspect(akasha)}`);
        try {
            const config = (await import(
                path.join(process.cwd(), configFN)
            )).default;
            let akasha = config.akasha;
            await akasha.setup(config);
            console.log(config.layoutDirs);
            await akasha.closeCaches();
        } catch (e) {
            console.error(`layoutsdirs command ERRORED ${e.stack}`);
        }
    });


program
    .command('documents <configFN> [rootPath]')
    .description('List the documents in a site configuration')
    .action(async (configFN, rootPath) => {
        // console.log(`render: akasha: ${util.inspect(akasha)}`);
        try {
            const config = (await import(
                path.join(process.cwd(), configFN)
            )).default;
            let akasha = config.akasha;
            await akasha.setup(config);
            console.log(await akasha.filecache.documentsCache.paths(rootPath));
            await akasha.closeCaches();
        } catch (e) {
            console.error(`documents command ERRORED ${e.stack}`);
        }
    });

program
    .command('docs-set-dates <configFN>')
    .description('Set the aTime and mTime for documents in a site configuration')
    .action(async (configFN) => {
        // console.log(`render: akasha: ${util.inspect(akasha)}`);
        try {
            const config = (await import(
                path.join(process.cwd(), configFN)
            )).default;
            let akasha = config.akasha;
            await akasha.setup(config);
            console.log(akasha.filecache.documentsCache.setTimes());
            await akasha.closeCaches();
        } catch (e) {
            console.error(`docs-set-dates command ERRORED ${e.stack}`);
        }
    });

program
    .command('docinfo <configFN> <docFN>')
    .description('Show information about a document in a site configuration')
    .action(async (configFN, docFN) => {
        // console.log(`render: akasha: ${util.inspect(akasha)}`);
        try {
            const config = (await import(
                path.join(process.cwd(), configFN)
            )).default;
            let akasha = config.akasha;
            await akasha.setup(config);
            const docinfo = await akasha.filecache.documentsCache.find(docFN);
            console.log(`docFN ${docFN} `, docinfo);
            await akasha.closeCaches();
        } catch (e) {
            console.error(`docinfo command ERRORED ${e.stack}`);
        }
    });

program
    .command('index-files <configFN> [rootPath]')
    .description('List the index pages (index.html) under the given directory')
    .action(async (configFN, rootPath) => {
        // console.log(`render: akasha: ${util.inspect(akasha)}`);
        try {
            const config = (await import(
                path.join(process.cwd(), configFN)
            )).default;
            let akasha = config.akasha;
            await akasha.setup(config);
            const docinfo = await akasha.filecache.documentsCache.indexFiles(rootPath);
            console.log(`indexes ${rootPath} `, docinfo.map(index => {
                return {
                    vpath: index.vpath,
                    renderPath: index.renderPath,
                    mountPoint: index.mountPoint,
                    pathInMounted: index.pathInMounted,
                    dirname: index.dirname
                }
            }));
            await akasha.closeCaches();
        } catch (e) {
            console.error(`index-files command ERRORED ${e.stack}`);
        }
    });

program
    .command('siblings <configFN> <vpath>')
    .description('List the sibling pages to the named document')
    .action(async (configFN, vpath) => {
        // console.log(`render: akasha: ${util.inspect(akasha)}`);
        try {
            const config = (await import(
                path.join(process.cwd(), configFN)
            )).default;
            let akasha = config.akasha;
            await akasha.setup(config);
            const docinfo = await akasha.filecache.documentsCache.siblings(vpath);
            console.log(`siblings ${vpath} `, docinfo.map(index => {
                return {
                    vpath: index.vpath,
                    renderPath: index.renderPath,
                    mountPoint: index.mountPoint,
                    pathInMounted: index.pathInMounted,
                    dirname: index.dirname
                }
            }));
            await akasha.closeCaches();
        } catch (e) {
            console.error(`siblings command ERRORED ${e.stack}`);
        }
    });

program
    .command('tags <configFN>')
    .description('List the tags')
    .action(async (configFN) => {
        // console.log(`render: akasha: ${util.inspect(akasha)}`);
        try {
            const config = (await import(
                path.join(process.cwd(), configFN)
            )).default;
            let akasha = config.akasha;
            await akasha.setup(config);
            console.log(await akasha.filecache.documentsCache.tags());
            await akasha.closeCaches();
        } catch (e) {
            console.error(`docinfo command ERRORED ${e.stack}`);
        }
    });

program
    .command('docs-with-tag <configFN> <tags...>')
    .description('List the document vpaths for given tags')
    .action(async (configFN, tags) => {
        try {
            const config = (await import(
                path.join(process.cwd(), configFN)
            )).default;
            let akasha = config.akasha;
            await akasha.setup(config);
            console.log(await akasha.filecache
                .documentsCache.documentsWithTag(tags));
            await akasha.closeCaches();
        } catch (e) {
            console.error(`docs-with-tags command ERRORED ${e.stack}`);
        }
    });

program
    .command('child-item-tree <configFN> <rootPath>')
    .description('List the documents under a given location by tree')
    .action(async (configFN, rootPath) => {

        try {
            const config = (await import(
                path.join(process.cwd(), configFN)
            )).default;
            let akasha = config.akasha;
            await akasha.setup(config);
            console.log(
                YAML.dump({
                    tree: await akasha.filecache
                        .documentsCache.childItemTree(rootPath)
                }, { indent: 4 })
                // .map(item => {
                //     return {
                //         vpath: item.vpath,
                //         renderPath: item.renderPath
                //     }
                // })
            );
            await akasha.closeCaches();
        } catch (e) {
            console.error(`docs-with-tags command ERRORED ${e.stack}`);
        }
    });

program
    .command('search <configFN>')
    .description('Search for documents')
    .option('--root <rootPath>', 'Select only files within the named directory')
    .option('--match <pathmatch>', 'Select only files matching the regular expression')
    .option('--rendermatch <renderpathmatch>', 'Select only files matching the regular expression')
    .option('--glob <globmatch>', 'Select only files matching the glob expression')
    .option('--renderglob <globmatch>', 'Select only files rendering to the glob expression')
    .option('--layout <layout>', 'Select only files matching the layouts')
    .option('--mime <mime>', 'Select only files matching the MIME type')
    .option('--tag <tag>', 'Select only files with the tag')
    .action(async (configFN, cmdObj) => {
        // console.log(`render: akasha: ${util.inspect(akasha)}`);
        try {
            const config = (await import(
                path.join(process.cwd(), configFN)
            )).default;
            let akasha = config.akasha;
            await akasha.setup(config);
            // console.log(cmdObj);
            let options: any = { };
            if (cmdObj.root) options.rootPath = cmdObj.root;
            if (cmdObj.match) options.pathmatch = cmdObj.match;
            if (cmdObj.rendermatch) options.renderpathmatch = cmdObj.rendermatch;
            if (cmdObj.glob) options.glob = cmdObj.glob;
            if (cmdObj.renderglob) options.renderglob = cmdObj.renderglob;
            if (cmdObj.layout) options.layouts = [ cmdObj.layout ];
            if (cmdObj.mime) options.mime = cmdObj.mime;
            if (cmdObj.tag) options.tag = cmdObj.tag;
            // console.log(options);
            let docs = await akasha.filecache.documentsCache.search(options);
            console.log(docs
            // .map(doc => {
            //     return {
            //         vpath: doc.vpath,
            //         renderPath: doc.renderPath,
            //         dirname: doc.dirname
            //     }
            // })
            // .sort((a: any, b: any) => {
            //     var tagA = a.dirname.toLowerCase();
            //     var tagB = b.dirname.toLowerCase();
            //     if (tagA < tagB) return -1;
            //     if (tagA > tagB) return 1;
            //     return 0;
            // })
            // .reverse()
            );
            await akasha.closeCaches();
        } catch (e) {
            console.error(`search command ERRORED ${e.stack}`);
        }
    });

program
    .command('assets <configFN>')
    .description('List the assets in a site configuration')
    .action(async (configFN) => {
        // console.log(`render: akasha: ${util.inspect(akasha)}`);
        try {
            const config = (await import(
                path.join(process.cwd(), configFN)
            )).default;
            let akasha = config.akasha;
            await akasha.setup(config);
            console.log(await akasha.filecache.assetsCache.paths());
            await akasha.closeCaches();
        } catch (e) {
            console.error(`assets command ERRORED ${e.stack}`);
        }
    });

program
    .command('assetinfo <configFN> <docFN>')
    .description('Show information about an asset in a site configuration')
    .action(async (configFN, assetFN) => {
        // console.log(`render: akasha: ${util.inspect(akasha)}`);
        try {
            const config = (await import(
                path.join(process.cwd(), configFN)
            )).default;
            let akasha = config.akasha;
            await akasha.setup(config);
            const assetinfo = await akasha.filecache.assetsCache.find(assetFN);
            console.log(assetinfo);
            await akasha.closeCaches();
        } catch (e) {
            console.error(`assetinfo command ERRORED ${e.stack}`);
        }
    });

program
    .command('layouts <configFN>')
    .description('List the layouts in a site configuration')
    .action(async (configFN) => {
        // console.log(`render: akasha: ${util.inspect(akasha)}`);
        try {
            const config = (await import(
                path.join(process.cwd(), configFN)
            )).default;
            let akasha = config.akasha;
            await akasha.setup(config);
            console.log(await akasha.filecache.layoutsCache.paths());
            await akasha.closeCaches();
        } catch (e) {
            console.error(`layouts command ERRORED ${e.stack}`);
        }
    });

// TODO both test.html and test.html.njk match
//      This is probably incorrect, since we do not render these files
//      The partials directory has the same problem
//      Some kind of flag on creating the FileCache to change the behavior

program
    .command('layoutinfo <configFN> <docFN>')
    .description('Show information about a layout in a site configuration')
    .action(async (configFN, layoutFN) => {
        // console.log(`render: akasha: ${util.inspect(akasha)}`);
        try {
            const config = (await import(
                path.join(process.cwd(), configFN)
            )).default;
            let akasha = config.akasha;
            await akasha.setup(config);
            const layoutinfo = await akasha.filecache.layoutsCache.find(layoutFN);
            console.log(layoutinfo);
            await akasha.closeCaches();
        } catch (e) {
            console.error(`layoutinfo command ERRORED ${e.stack}`);
        }
    });

program
    .command('partials <configFN>')
    .description('List the partials in a site configuration')
    .action(async (configFN) => {
        // console.log(`render: akasha: ${util.inspect(akasha)}`);
        try {
            const config = (await import(
                path.join(process.cwd(), configFN)
            )).default;
            let akasha = config.akasha;
            await akasha.setup(config);
            console.log(await akasha.filecache.partialsCache.paths());
            await akasha.closeCaches();
        } catch (e) {
            console.error(`partials command ERRORED ${e.stack}`);
        }
    });

// TODO both test.html and test.html.njk match
//      This is probably incorrect, since we do not render these files

program
    .command('partialinfo <configFN> <docFN>')
    .description('Show information about a partial in a site configuration')
    .action(async (configFN, partialFN) => {
        // console.log(`render: akasha: ${util.inspect(akasha)}`);
        try {
            const config = (await import(
                path.join(process.cwd(), configFN)
            )).default;
            let akasha = config.akasha;
            await akasha.setup(config);
            const partialinfo = await akasha.filecache.partialsCache.find(partialFN);
            console.log(partialinfo);
            await akasha.closeCaches();
        } catch (e) {
            console.error(`partialinfo command ERRORED ${e.stack}`);
        }
    });

program
    .command('index <configFN>')
    .description('Loads configuration, indexes content, then exits')
    .action(async (configFN) => {
        try {
            const config = (await import(
                path.join(process.cwd(), configFN)
            )).default;
            let akasha = config.akasha;
            await akasha.setup(config);
            await akasha.closeCaches();
        } catch (e) {
            console.error(`partialinfo command ERRORED ${e.stack}`);
        }
    });

program.parse(process.argv);
