import { a as getRenderingRef, f as forceUpdate, h } from './index-08b73e9e.js';

const appendToMap = (map, propName, value) => {
    const items = map.get(propName);
    if (!items) {
        map.set(propName, [value]);
    }
    else if (!items.includes(value)) {
        items.push(value);
    }
};
const debounce = (fn, ms) => {
    let timeoutId;
    return (...args) => {
        if (timeoutId) {
            clearTimeout(timeoutId);
        }
        timeoutId = setTimeout(() => {
            timeoutId = 0;
            fn(...args);
        }, ms);
    };
};

/**
 * Check if a possible element isConnected.
 * The property might not be there, so we check for it.
 *
 * We want it to return true if isConnected is not a property,
 * otherwise we would remove these elements and would not update.
 *
 * Better leak in Edge than to be useless.
 */
const isConnected = (maybeElement) => !('isConnected' in maybeElement) || maybeElement.isConnected;
const cleanupElements = debounce((map) => {
    for (let key of map.keys()) {
        map.set(key, map.get(key).filter(isConnected));
    }
}, 2000);
const stencilSubscription = () => {
    if (typeof getRenderingRef !== 'function') {
        // If we are not in a stencil project, we do nothing.
        // This function is not really exported by @stencil/core.
        return {};
    }
    const elmsToUpdate = new Map();
    return {
        dispose: () => elmsToUpdate.clear(),
        get: (propName) => {
            const elm = getRenderingRef();
            if (elm) {
                appendToMap(elmsToUpdate, propName, elm);
            }
        },
        set: (propName) => {
            const elements = elmsToUpdate.get(propName);
            if (elements) {
                elmsToUpdate.set(propName, elements.filter(forceUpdate));
            }
            cleanupElements(elmsToUpdate);
        },
        reset: () => {
            elmsToUpdate.forEach((elms) => elms.forEach(forceUpdate));
            cleanupElements(elmsToUpdate);
        },
    };
};

const createObservableMap = (defaultState, shouldUpdate = (a, b) => a !== b) => {
    let states = new Map(Object.entries(defaultState !== null && defaultState !== void 0 ? defaultState : {}));
    const handlers = {
        dispose: [],
        get: [],
        set: [],
        reset: [],
    };
    const reset = () => {
        states = new Map(Object.entries(defaultState !== null && defaultState !== void 0 ? defaultState : {}));
        handlers.reset.forEach((cb) => cb());
    };
    const dispose = () => {
        // Call first dispose as resetting the state would
        // cause less updates ;)
        handlers.dispose.forEach((cb) => cb());
        reset();
    };
    const get = (propName) => {
        handlers.get.forEach((cb) => cb(propName));
        return states.get(propName);
    };
    const set = (propName, value) => {
        const oldValue = states.get(propName);
        if (shouldUpdate(value, oldValue, propName)) {
            states.set(propName, value);
            handlers.set.forEach((cb) => cb(propName, value, oldValue));
        }
    };
    const state = (typeof Proxy === 'undefined'
        ? {}
        : new Proxy(defaultState, {
            get(_, propName) {
                return get(propName);
            },
            ownKeys(_) {
                return Array.from(states.keys());
            },
            getOwnPropertyDescriptor() {
                return {
                    enumerable: true,
                    configurable: true,
                };
            },
            has(_, propName) {
                return states.has(propName);
            },
            set(_, propName, value) {
                set(propName, value);
                return true;
            },
        }));
    const on = (eventName, callback) => {
        handlers[eventName].push(callback);
        return () => {
            removeFromArray(handlers[eventName], callback);
        };
    };
    const onChange = (propName, cb) => {
        const unSet = on('set', (key, newValue) => {
            if (key === propName) {
                cb(newValue);
            }
        });
        const unReset = on('reset', () => cb(defaultState[propName]));
        return () => {
            unSet();
            unReset();
        };
    };
    const use = (...subscriptions) => {
        const unsubs = subscriptions.reduce((unsubs, subscription) => {
            if (subscription.set) {
                unsubs.push(on('set', subscription.set));
            }
            if (subscription.get) {
                unsubs.push(on('get', subscription.get));
            }
            if (subscription.reset) {
                unsubs.push(on('reset', subscription.reset));
            }
            if (subscription.dispose) {
                unsubs.push(on('dispose', subscription.dispose));
            }
            return unsubs;
        }, []);
        return () => unsubs.forEach((unsub) => unsub());
    };
    const forceUpdate = (key) => {
        const oldValue = states.get(key);
        handlers.set.forEach((cb) => cb(key, oldValue, oldValue));
    };
    return {
        state,
        get,
        set,
        on,
        onChange,
        use,
        dispose,
        reset,
        forceUpdate,
    };
};
const removeFromArray = (array, item) => {
    const index = array.indexOf(item);
    if (index >= 0) {
        array[index] = array[array.length - 1];
        array.length--;
    }
};

const createStore = (defaultState, shouldUpdate) => {
    const map = createObservableMap(defaultState, shouldUpdate);
    map.use(stencilSubscription());
    return map;
};

let defaultRouter;
const createRouter = (opts) => {
    var _a;
    const win = window;
    const url = new URL(win.location.href);
    const parseURL = (_a = opts === null || opts === void 0 ? void 0 : opts.parseURL) !== null && _a !== void 0 ? _a : DEFAULT_PARSE_URL;
    const { state, onChange, dispose } = createStore({
        url,
        activePath: parseURL(url)
    }, (newV, oldV, prop) => {
        if (prop === 'url') {
            return newV.href !== oldV.href;
        }
        return newV !== oldV;
    });
    const push = (href) => {
        history.pushState(null, null, href);
        const url = new URL(href, document.baseURI);
        state.url = url;
        state.activePath = parseURL(url);
    };
    const match = (routes) => {
        const { activePath } = state;
        for (let route of routes) {
            const params = matchPath(activePath, route.path);
            if (params) {
                if (route.to != null) {
                    const to = (typeof route.to === 'string')
                        ? route.to
                        : route.to(activePath);
                    push(to);
                    return match(routes);
                }
                else {
                    return { params, route };
                }
            }
        }
        return undefined;
    };
    const navigationChanged = () => {
        const url = new URL(win.location.href);
        state.url = url;
        state.activePath = parseURL(url);
    };
    const Switch = (_, childrenRoutes) => {
        const result = match(childrenRoutes);
        if (result) {
            if (typeof result.route.jsx === 'function') {
                return result.route.jsx(result.params);
            }
            else {
                return result.route.jsx;
            }
        }
    };
    const disposeRouter = () => {
        defaultRouter = undefined;
        win.removeEventListener('popstate', navigationChanged);
        dispose();
    };
    const router = defaultRouter = {
        Switch,
        get url() {
            return state.url;
        },
        get activePath() {
            return state.activePath;
        },
        push,
        onChange: onChange,
        dispose: disposeRouter,
    };
    // Initial update
    navigationChanged();
    // Listen URL changes
    win.addEventListener('popstate', navigationChanged);
    return router;
};
const Route = (props, children) => {
    var _a;
    if ('to' in props) {
        return {
            path: props.path,
            to: props.to,
        };
    }
    return {
        path: props.path,
        id: props.id,
        jsx: (_a = props.render) !== null && _a !== void 0 ? _a : children,
    };
};
const href = (href, router = defaultRouter) => {
    return {
        href,
        onClick: (ev) => {
            if (ev.metaKey || ev.ctrlKey) {
                return;
            }
            if (ev.which == 2 || ev.button == 1) {
                return;
            }
            ev.preventDefault();
            router.push(href);
        },
    };
};
const matchPath = (pathname, path) => {
    if (typeof path === 'string') {
        if (path === pathname) {
            return {};
        }
    }
    else if (typeof path === 'function') {
        const params = path(pathname);
        if (params) {
            return params === true
                ? {}
                : { ...params };
        }
    }
    else {
        const results = path.exec(pathname);
        if (results) {
            path.lastIndex = 0;
            return { ...results };
        }
    }
    return undefined;
};
const DEFAULT_PARSE_URL = (url) => {
    return url.pathname.toLowerCase();
};

const Router = createRouter();
const App = (attributes, nodes, utils) => {
  var _a;
  const emptyNode = Object.entries(nodes[0]).reduce((r, entry) => {
    r[entry[0]] = null;
    return r;
  }, {});
  const emptyChild = nodeToChild(emptyNode);
  function nodeToChild(node) {
    let result;
    utils.forEach([node], child => (result = child));
    return result !== null && result !== void 0 ? result : emptyChild;
  }
  function childToNode(child) {
    return utils.map([emptyNode], c => (Object.assign(Object.assign({}, c), child)))[0];
  }
  const children = nodes.map((node, index) => (Object.assign(Object.assign({}, nodeToChild(node)), { node })));
  return (h("smoothly-app", null,
    h("header", null,
      h("h1", null,
        h("a", Object.assign({}, href((_a = resolve("")) !== null && _a !== void 0 ? _a : "/")), attributes.label)),
      children.filter(child => { var _a; return ((_a = child.vattrs) === null || _a === void 0 ? void 0 : _a.slot) == "header"; }).map(child => child.node),
      h("nav", null,
        h("ul", null, utils
          .map([
          ...children.filter(child => { var _a; return ((_a = child.vattrs) === null || _a === void 0 ? void 0 : _a.slot) == "nav-start"; }),
          ...children.filter(child => { var _a, _b; return ((_a = child.vattrs) === null || _a === void 0 ? void 0 : _a.label) && ((_b = child.vattrs) === null || _b === void 0 ? void 0 : _b.path); }),
          ...children.filter(child => { var _a; return ((_a = child.vattrs) === null || _a === void 0 ? void 0 : _a.slot) == "nav-end"; }),
        ].map(child => child.node), child => {
          var _a, _b, _c, _d, _e, _f, _g, _h, _j;
          if (((_a = child.vattrs) === null || _a === void 0 ? void 0 : _a.label) && ((_b = child.vattrs) === null || _b === void 0 ? void 0 : _b.path))
            child = Object.assign(Object.assign({}, emptyChild), { vtag: "a", vattrs: { href: (_c = child.vattrs) === null || _c === void 0 ? void 0 : _c.path }, vchildren: [
                ((_d = child.vattrs) === null || _d === void 0 ? void 0 : _d.icon)
                  ? childToNode({
                    vtag: "smoothly-icon",
                    vattrs: {
                      toolTip: (_e = child.vattrs) === null || _e === void 0 ? void 0 : _e.label,
                      name: (_f = child.vattrs) === null || _f === void 0 ? void 0 : _f.icon,
                      size: "medium",
                    },
                  })
                  : childToNode({ vtext: (_g = child.vattrs) === null || _g === void 0 ? void 0 : _g.label }),
              ] });
          const url = resolve((_h = child.vattrs) === null || _h === void 0 ? void 0 : _h.href);
          return child.vtag != "a"
            ? child
            : url
              ? Object.assign(Object.assign({}, child), { vattrs: Object.assign(Object.assign({}, child.vattrs), { class: [(_j = child.vattrs) === null || _j === void 0 ? void 0 : _j.class, Router.activePath == url ? "active" : ""].join(" ") || undefined, href: url }) }) : Object.assign(Object.assign({}, child), { vattrs: Object.assign(Object.assign({}, child.vattrs), { target: "new" }) });
        })
          .map(node => {
          var _a, _b;
          const child = nodeToChild(node);
          return child.vtag == "a" && !((_a = child.vattrs) === null || _a === void 0 ? void 0 : _a.target) ? (h("a", Object.assign({}, child.vattrs, href((_b = child.vattrs) === null || _b === void 0 ? void 0 : _b.href)), child.vchildren)) : (node);
        })
          .map(child => (h("li", null, child)))))),
    h("content", null,
      h(Router.Switch, null, children
        .filter(child => { var _a; return ((_a = child.vattrs) === null || _a === void 0 ? void 0 : _a.path) != undefined; })
        .map(child => {
        var _a, _b, _c, _d, _e, _f, _g, _h;
        return ((_a = child.vattrs) === null || _a === void 0 ? void 0 : _a.to) ? (h(Route, { path: (_c = resolve((_b = child.vattrs) === null || _b === void 0 ? void 0 : _b.path)) !== null && _c !== void 0 ? _c : (_d = child.vattrs) === null || _d === void 0 ? void 0 : _d.path, to: (_e = child.vattrs) === null || _e === void 0 ? void 0 : _e.to })) : (h(Route, { path: (_g = resolve((_f = child.vattrs) === null || _f === void 0 ? void 0 : _f.path)) !== null && _g !== void 0 ? _g : (_h = child.vattrs) === null || _h === void 0 ? void 0 : _h.path }, child.node));
      })))));
};
function resolve(url) {
  const u = new URL(url, document.baseURI);
  return url == ""
    ? new URL(document.baseURI).pathname
    : u.toString().startsWith(document.baseURI)
      ? u.pathname
      : undefined;
}

export { App as A };
